diff --git a/docs/git-token-design.md b/docs/git-token-design.md new file mode 100644 index 0000000..36c94e0 --- /dev/null +++ b/docs/git-token-design.md @@ -0,0 +1,360 @@ +> Grounded in PR #4: Gecko now asks Fence for GitHub installation tokens and stores only repository/installation/sync state, not GitHub private keys or tokens. + +# Gecko GitHub Key and Token Architecture + +## Summary + +Gecko does not mint or store GitHub App private keys directly. Instead, Gecko delegates GitHub App credential brokering to Fence. + +The model is: + +1. User authenticates to Gecko with a bearer token. +2. Gecko authorizes the user against project or organization resources. +3. Gecko calls Fence at `/credentials/github`. +4. Fence validates the user and GitHub App installation context. +5. Fence returns a short-lived GitHub installation access token. +6. Gecko uses that token immediately for GitHub read/write operations. +7. Gecko persists repository state, installation metadata, mirror paths, sync status, and upload/PR metadata. +8. Gecko should not persist GitHub access tokens or GitHub App private keys. + +## Core Components + +```mermaid +flowchart LR + User[User / Frontend] + Gecko[Gecko API] + Authz[Fence / Arborist Authz] + Fence[Fence GitHub Credential Broker] + GitHub[GitHub App / GitHub API] + DB[(Postgres config_schema)] + FS[(Git data dir / local mirrors)] + + User -->|Bearer token| Gecko + Gecko -->|Check project/org access| Authz + Gecko -->|POST /credentials/github| Fence + Fence -->|Uses GitHub App private key| GitHub + GitHub -->|Installation access token| Fence + Fence -->|Short-lived token| Gecko + Gecko -->|GitHub API / git clone / PR ops| GitHub + Gecko -->|Project + installation state| DB + Gecko -->|Repository mirrors| FS +``` + +## Credential Boundaries + +### Gecko owns + +Gecko owns application-level Git repository orchestration: + +* project Git configuration +* project-to-repository mapping +* GitHub installation metadata +* repository mirror path +* default branch +* sync state +* last refresh/error state +* upload sessions +* generated pull request metadata + +### Fence owns + +Fence owns GitHub App credential brokering: + +* GitHub App private key +* GitHub App authentication +* installation token minting +* install URL generation +* installation status checks +* installation repository listing + +### GitHub owns + +GitHub owns: + +* GitHub App installation +* repository permissions +* short-lived installation access tokens +* repository refs, trees, commits, branches, and pull requests + +## Token Minting Flow + +```mermaid +sequenceDiagram + participant U as User / Frontend + participant G as Gecko + participant A as Arborist / Fence Authz + participant F as Fence GitHub Broker + participant GH as GitHub API + + U->>G: Git request with Authorization: Bearer user-token + G->>A: Check user access to /programs/{org}/projects/{project} + A-->>G: Allowed / denied + + G->>F: POST /credentials/github
action=installation_token
organization, project, owner, repo, access=read/write + F->>GH: Authenticate as GitHub App + F->>GH: Request installation access token + GH-->>F: Short-lived installation token + F-->>G: token + expiry + repository metadata + + G->>GH: Use token for GitHub API or git transport + GH-->>G: Repository data / write result +``` + +## Read Token Usage + +Read tokens are requested when Gecko needs to: + +* refresh a local repository mirror +* read GitHub file metadata +* inspect repository contents +* list refs +* reconcile repository installation status + +The token is used as a GitHub installation token, commonly with: + +```text +username: x-access-token +password: +``` + +for Git transport operations. + +## Write Token Usage + +Write tokens are requested when Gecko needs to create GitHub-side changes, especially upload finalization. + +During upload finalization Gecko: + +1. validates the upload session +2. confirms all files have DRS object IDs and checksums +3. asks Fence for a write-scoped installation token +4. creates Git LFS pointer files +5. creates a GitHub tree +6. creates a commit +7. creates a branch +8. opens a pull request +9. stores the resulting commit SHA and pull request URL + +```mermaid +sequenceDiagram + participant U as User + participant G as Gecko + participant F as Fence + participant GH as GitHub + participant DB as Postgres + + U->>G: POST upload session finalize + G->>DB: Load upload session + files + G->>F: Request write installation token + F-->>G: Short-lived write token + G->>GH: Get base ref + G->>GH: Create tree with LFS pointer files + G->>GH: Create commit + G->>GH: Create branch + G->>GH: Create pull request + GH-->>G: commit SHA + PR URL + G->>DB: Persist finalized session metadata +``` + +## What Gecko Stores + +Gecko persists Git-related state in Postgres under `config_schema`. + +### `git_project_state` + +Stores repository and sync metadata: + +* `project_id` +* `repo_host` +* `repo_owner` +* `repo_name` +* `installation_id` +* `installation_target_type` +* `installation_target` +* `mirror_path` +* `sync_state` +* `default_branch` +* `last_refreshed_at` +* `last_error` + +This table stores installation identity and sync state, not GitHub tokens. + +### `git_organization_state` + +Stores organization-level installation/configuration state: + +* organization installation status +* target metadata +* repository selection +* timestamps +* last error + +### `git_upload_session` + +Stores staged upload workflow state: + +* session ID +* project ID +* base branch +* generated upload branch +* PR title/body +* status +* final commit SHA +* pull request URL + +### `git_upload_session_file` + +Stores file-level upload state: + +* file name +* target path +* size +* checksum +* DRS object ID +* status +* collision/error state + +### Local filesystem + +Gecko stores local bare mirrors under the configured Git data directory: + +```text +{GIT_DATA_DIR}/{host}/{owner}/{repo}.git +``` + +These mirrors allow Gecko to serve tree, refs, and file metadata without treating GitHub as the only read path. + +## What Gecko Should Not Store + +Gecko should not persist: + +* GitHub App private keys +* GitHub installation access tokens +* GitHub user tokens +* long-lived GitHub credentials +* write tokens after a request completes + +The only durable GitHub-related values Gecko should store are identifiers and derived state such as installation ID, repository identity, mirror path, default branch, sync status, commit SHA, and PR URL. + +## Security Model (High-level) + +```mermaid +flowchart TD + Request[Incoming Gecko request] + Bearer[Bearer user token] + Authz[Arborist/Fence resource authorization] + Resource["Resource /programs/org/projects/project"] + Broker[Fence GitHub credential broker] + Token[Short-lived GitHub installation token] + GitOp[GitHub API / Git operation] + Persist[Persist non-secret state] + + Request --> Bearer + Bearer --> Authz + Authz --> Resource + Resource -->|allowed| Broker + Broker --> Token + Token --> GitOp + GitOp --> Persist +``` + +Authorization and GitHub access are separate concerns: + +* User authorization decides whether the caller may act on a CALYPR project. +* Fence decides whether that authorized caller may receive GitHub installation access. +* GitHub installation permissions decide what repository actions the token can perform. + +## Security Model (Expanded) + +```mermaid +sequenceDiagram + participant U as User / CALYPR Frontend + participant G as Gecko API + participant A as Fence / Arborist Authz + participant F as Fence GitHub Broker + participant GH as GitHub API + participant DB as Postgres + participant FS as Git Data Dir + + U->>G: Request with Authorization: Bearer user-token + + G->>A: Check access to /programs/{org}/projects/{project} + A-->>G: allowed / denied + + alt User is not authorized + G-->>U: 403 Forbidden + else User is authorized + G->>F: POST /credentials/github
action=installation_token
organization, project, owner, repo, access=read/write + F->>GH: Authenticate as GitHub App
using GitHub App private key + F->>GH: POST /app/installations/{installation_id}/access_tokens + GH-->>F: Short-lived installation access token + F-->>G: installation token + expiry + repository metadata + + alt Read workflow + G->>GH: GET /repos/{owner}/{repo} + GH-->>G: repository metadata + G->>GH: git fetch/clone using x-access-token + GH-->>G: repository objects + G->>FS: Update local bare mirror + G->>DB: Store repo identity, installation ID, mirror path, default branch, sync state + G-->>U: Project Git status / tree / file metadata + else Write workflow + G->>GH: GET /repos/{owner}/{repo}/git/ref/heads/{baseBranch} + GH-->>G: base ref + G->>GH: GET /repos/{owner}/{repo}/git/commits/{baseSha} + GH-->>G: base commit + G->>GH: POST /repos/{owner}/{repo}/git/trees + GH-->>G: new tree SHA + G->>GH: POST /repos/{owner}/{repo}/git/commits + GH-->>G: new commit SHA + G->>GH: POST /repos/{owner}/{repo}/git/refs + GH-->>G: new branch ref + G->>GH: POST /repos/{owner}/{repo}/pulls + GH-->>G: pull request URL + G->>DB: Store upload session status, commit SHA, PR URL + G-->>U: Finalized upload session + end + end +``` + +### Key security properties + +* Gecko validates the caller’s bearer token before requesting GitHub access. +* Gecko asks Fence for short-lived GitHub installation tokens through `POST /credentials/github`. +* Fence, not Gecko, owns the GitHub App private key and performs token minting. +* Gecko uses GitHub installation tokens only for immediate GitHub API or git transport calls. +* Gecko persists non-secret state only: repository identity, installation ID, mirror path, default branch, sync status, upload session state, commit SHA, and pull request URL. +* Gecko should not persist GitHub App private keys, user tokens, or GitHub installation access tokens. + +This reflects the PR’s Fence broker flow and the actual GitHub calls used during read/write workflows. + + +## Operational Notes + +Required runtime configuration includes: + +```text +GIT_DATA_DIR +FENCE_BASE_URL +GITHUB_API_BASE_URL +``` + +`GIT_DATA_DIR` is required for Git-enabled Gecko operation because repository mirrors and thumbnails are filesystem-backed. + +## Architecture Decision + +Gecko should continue to avoid direct ownership of GitHub App private keys. + +Fence should remain the only component responsible for GitHub App key custody and installation token minting. Gecko should request short-lived read/write installation tokens only when needed, use them immediately, and persist only non-secret Git repository lifecycle state. + +## Review Checklist + +* Confirm no installation tokens are written to Postgres. +* Confirm no GitHub App private key is configured in Gecko. +* Confirm logs do not include installation tokens. +* Confirm write tokens are requested only for write workflows. +* Confirm read tokens are sufficient for mirror refresh and file metadata reads. +* Confirm project authorization happens before token brokerage. +* Confirm organization-level access is checked before install/connect/reconcile flows. +* Confirm stored installation IDs are treated as identifiers, not credentials. +