.gitbay/wiki/Architecture/04-Trust-Boundaries.org

bd5cf5d7d1f34fa780660fd7562b9ffd9746ee27
gitbay/.gitbay/wiki/Architecture/04-Trust-Boundaries.org rendered · source · history · blame · raw

135 lines · 8819 bytes

  1#+title: Trust boundaries and data flows
  2
  3[[file:diagrams/04-trust-boundaries.svg]]
  4
  5* Zones
  6
  7| Zone | Contents                                                        | Trust                                                     |
  8|------+-----------------------------------------------------------------+-----------------------------------------------------------|
  9| Z0   | The internet: visitors, clients, webhook and mirror endpoints   | none                                                      |
 10| Z1   | gitbayd process                                                 | holds all policy; trusted                                 |
 11| Z2   | Local state: SQLite, repositories, LFS, keys, config           | trusted; readable by the =gitbay= user                    |
 12| Z3   | git subprocesses and hook processes                             | run as =gitbay= on data from Z0; their decisions come from Z1 |
 13| Z4   | CI runner service                                               | trusted to report honestly; holds secrets for trusted builds |
 14| Z5   | CI containers                                                   | untrusted code from repositories and forks                |
 15| Z6   | Operator host access                                            | root; outside every in-application control                |
 16
 17* Boundaries
 18
 19| ID  | Boundary                           | What crosses                                     | Control at the boundary                                                                 |
 20|-----+------------------------------------+--------------------------------------------------+-----------------------------------------------------------------------------------------|
 21| TB1 | Z0 → Z1 SSH                        | key auth, exec requests, git packs               | public-key auth, per-IP failure limit (=internal/sshd/sshd.go=, =ratelimit.go=); unknown keys reach only =register= |
 22| TB2 | Z0 → Z1 HTTPS                      | page requests, form posts, API calls, fetches, LFS | TLS; session cookie or bearer token; =checkOrigin= on posts; CSP and security headers (=internal/httpd/routes.go=); smart HTTP is fetch-only (=smart.go=) |
 23| TB3 | identity → data                    | every command                                    | =Dispatch= gates, then =resolveRepo= with =policy= predicates; unreadable repositories are indistinguishable from missing ones ([[file:05-Identity-and-Access.org][5]]) |
 24| TB4 | Z1 → Z3 git                        | argv, repository path, stdin packs               | argv built by code, never a shell; repository path from the database, not the request (=internal/gitutil=) |
 25| TB5 | Z3 → Z1 hook socket                | ref updates, repository id, user id, key scope, push token, commit objects | the socket is mode 0600 and, on Linux, refuses a peer whose uid is not the daemon's; a request must carry the token sshd minted for its receive-pack (stored hashed in =push_tokens=) and name the same repository, account and scope. The daemon then decides with =policy.CheckPush= and =sig.VerifyCommit= (=internal/hookd/hookd.go=) |
 26| TB6 | Z4 ↔ Z1 runner channel             | build claims (with secrets for trusted builds), logs, results | runner-scoped SSH key; claims limited to attached repositories; secrets only when the build is trusted (=internal/control/build.go=) |
 27| TB7 | Z5 → Z4 container                  | build steps, workspace, build home               | rootless podman, operator-provisioned image, cgroup limits; a trusted build's home is its repository's, an untrusted build's is discarded with it; outbound is open; on the host only the forge's public ports (#260) |
 28| TB8 | Z1 → Z0 outbound                   | webhooks, mirrors, mail, push                    | address checks on user-supplied URLs; HMAC on webhooks; no redirects ([[file:03-Deployment.org][3]]) |
 29| TB9 | user content → browser             | Markdown and Org bodies, READMEs, filenames      | HTML sanitised (=ugcHTML=, =internal/httpd/web.go=, bluemonday); CSP =script-src 'none'= |
 30| TB10| Z6 → everything                    | host shell                                       | operator SSH on 2222, keys only, fail2ban; append-only offsite backup credentials |
 31
 32* Flows
 33
 34Each flow lists its hops in order. Boundary IDs refer to the table
 35above.
 36
 37** A. SSH control command
 38
 391. Client opens SSH; =authenticate= looks up the key fingerprint and
 40   records user id, key id and scope in the connection
 41   (=internal/sshd/sshd.go=). TB1.
 422. Each exec request: =runExec= reloads the account, touches the key's
 43   last-used time, calls =Exec= (=sshd.go=).
 443. =Exec= tokenizes the command line (no shell) and routes git
 45   transport verbs to =runGit=, everything else to =control.Dispatch=
 46   with =Source= set to the key fingerprint (=sshd.go=).
 474. =Dispatch= gates and runs the handler; mutating successes are
 48   audited (=internal/control/control.go=). TB3.
 49
 50** B. git push over SSH
 51
 52[[file:diagrams/06-push-flow.svg]]
 53
 541. =runGit= resolves the repository, applies the deploy-key or account
 55   checks, archive and pull-mirror refusals and the owner's storage
 56   quota (=sshd.go=). TB3.
 572. =git receive-pack= runs with the hook socket path, repository id,
 58   user id and key scope in its environment, and a push token (=sshd.go=). TB4.
 593. git runs =pre-receive=, which is =gitbayd hook pre-receive=. It
 60   reads the ref updates, computes ancestry in git's quarantine
 61   environment and asks the daemon over the socket
 62   (=cmd/gitbayd/hook.go=). TB5.
 634. The daemon applies =policy.CheckPush= (protected branches,
 64   =require-mr=, protected tags, server-owned =refs/merge-requests/*=)
 65   and, when the repository requires signed commits, asks for every
 66   incoming commit object and verifies each
 67   (=internal/hookd/hookd.go=).
 685. On refusal the hook exits 1 and git rejects the push atomically.
 696. On success git runs =post-receive=; the daemon records events,
 70   marks mirrors dirty, queues CI, syncs merge request heads, processes
 71   =Closes #N= references and audits force pushes
 72   (=hookd.go=).
 73
 74The server's own merge of a merge request does not pass through the
 75hooks: =runMRMerge= updates the ref with a compare-and-swap
 76(=internal/control/mr.go=) after =MergeGates=
 77(=mr.go=). When signed commits are required only fast-forward
 78merges are allowed, so the server never writes an unsigned commit
 79(=mr.go=).
 80
 81** C. Fetch over smart HTTP
 82
 83=GET info/refs= and =POST git-upload-pack= serve public repositories
 84only; a private repository answers 404. =git-receive-pack= over HTTP
 85always answers a pkt-line refusal, so there is no password prompt and
 86no HTTP write path (=internal/httpd/smart.go=,
 87=routes.go=). TB2.
 88
 89** D. Web read and write
 90
 911. The session cookie is hashed and looked up (=accounts.go=).
 922. Pages dispatch read commands into the registry with =Source=web= and
 93   decode the JSON result into the template (=internal/httpd/control.go=).
 943. Form posts pass =checkOrigin= (=accounts.go=), dispatch the
 95   matching command, and map the exit code to a redirect or an error on
 96   the page. Three toggles (pin, watch, mark read) write the store
 97   directly instead (#261).
 98
 99** E. JSON API
100
1011. =apiAuth= hashes the bearer token and looks it up; any failure is a
102   uniform 401 (=internal/httpd/api.go=).
1032. Per-account rate limit, with writes at a tenth of the read budget.
1043. =POST /api/v1/cmd= dispatches any command except git transport;
105   =GET /api/v1/read= refuses anything not marked =ReadOnly=, so a GET
106   cannot write (=apiread.go=).
1074. Exit codes map to HTTP status: 0→200, 2→400, 3→404, 4→403, else 500.
108
109** F. LFS
110
111=git-lfs-authenticate= over SSH applies the same repository checks as
112git transport and returns a one-hour HMAC token scoped to repository,
113operation and the SSH key that asked for it, deploy keys included
114(=internal/sshd/lfs.go=, =internal/lfs/lfs.go=). The HTTP batch, upload
115and download endpoints verify that token and that its key is still
116registered, unexpired and on an enabled account (=store.LiveSSHKeys=),
117and repeat the repository check for the key on each request; public
118repositories allow anonymous download. Objects are verified against
119their SHA-256 id on upload.
120
121** G. Browser login by emailed link
122
1231. =/login= takes a username or email; per-IP and per-account limits
124   (5 per hour) apply, and every non-eligible case returns the same
125   response (=internal/control/loginlink.go=).
1262. A 32-byte token is mailed; only its hash is stored, valid 15 minutes.
1273. =/login?token== consumes it atomically, rechecks the account and
128   sets the session cookie (=accounts.go=).
129
130=ssh git@host web login= mints the same kind of link, valid 5 minutes,
131to an already-authenticated key (=internal/control/web.go=).
132
133** H. CI build
134
135See [[file:07-CI-and-Supply-Chain.org][7. CI and supply chain]].