.gitbay/wiki/Architecture/05-Identity-and-Access.org

0338e6ace3de199d5fc383852649919b68ef3e42
gitbay/.gitbay/wiki/Architecture/05-Identity-and-Access.org rendered · source · history · blame · raw

134 lines · 9784 bytes

  1#+title: Identity and access
  2
  3[[file:diagrams/05-authorization.svg]]
  4
  5* Accounts
  6
  7| Property     | Values / behaviour                                                                  | Code |
  8|--------------+-------------------------------------------------------------------------------------+------|
  9| Registration | =closed= (host bootstrap only), =invite= (single-use code bound to an email), =open= (email required) | =internal/control/register.go= |
 10| Pending      | open registrations start pending until email is verified; a pending account may run only =email verify=, =email add=, =whoami=, =help=, and has no git access | =internal/control/control.go=, =internal/sshd/sshd.go= |
 11| Disabled     | refused in =Dispatch=, in SSH exec and at login-link redemption                     | =control.go= |
 12| Admin        | =users.is_admin=; set only by =admin user create --admin= or promotion by an admin; gates the whole =admin= noun | =control.go= |
 13
 14* Credentials
 15
 16| Credential         | Format and generation                           | Stored as                        | Scope                                      | Expiry                        | Revocation                          |
 17|--------------------+-------------------------------------------------+----------------------------------+--------------------------------------------+-------------------------------+-------------------------------------|
 18| SSH user key       | user's public key                               | fingerprint and public blob      | =full=, =git=, or =runner=                 | optional =--ttl=, refused at auth | =keys remove= (own keys); closes its connections |
 19| Deploy key         | public key                                      | same table, scope =deploy:<repo>:ro/rw= | one repository, read or read-write  | optional =--ttl=, refused at auth | =repo deploy-key remove= (repo admin); closes its connections |
 20| API token          | =gb_= + 32 random bytes hex                     | SHA-256 hash                     | =read= (default) or =full=; with an expiry, no credential-minting command | optional =--ttl=              | =token revoke [--created]=          |
 21| Web session        | 32 random bytes hex, cookie =gitbay_session=    | SHA-256 hash                     | full account                               | 12 h idle, 7 days absolute    | logout, =web sessions revoke=       |
 22| Login link         | 32 random bytes hex in a URL                    | SHA-256 hash, single use         | creates a web session                      | 15 min (mail), 5 min (SSH)    | consumed on use                     |
 23| Email verification | 32 random bytes hex                             | SHA-256 hash, single use         | verifies one address for one account       | 24 h                          | consumed on use                     |
 24| Invite             | random code                                     | SHA-256 hash, single use         | one registration for one email             | as issued                     | consumed on use                     |
 25| LFS transfer token | HMAC-SHA256 over repo, operation, expiry        | not stored (stateless)           | one repository, upload or download         | 1 h                           | expiry only                         |
 26
 27Generation and hashing: =internal/store/sessions.go= (=NewToken=,
 28=HashToken=, =crypto/rand=). Cookie attributes: =HttpOnly=,
 29=SameSite=Lax=, =Secure= unless TLS is off, =MaxAge= 7 days
 30(the session itself also ends after 12 hours idle)
 31(=internal/httpd/accounts.go=). Token scope values are
 32constrained by a database =CHECK= as well as the command
 33(=internal/store/migrations/0004_api_tokens.up.sql=).
 34
 35What the scopes allow:
 36
 37| Scope          | Control commands                | git transport                     |
 38|----------------+---------------------------------+-----------------------------------|
 39| =full= key     | all the account may run         | read and write                    |
 40| =git= key      | none                            | read and write                    |
 41| =runner= key   | =runner *= only, on attached repositories | read (clone)            |
 42| =deploy:*= key | none                            | its repository only, =ro= or =rw= |
 43| =full= token   | all the account may run         | not over the API                  |
 44| =read= token   | commands marked =ReadOnly=      | not over the API                  |
 45
 46Sources: =internal/control/control.go=,
 47=internal/policy/access.go=.
 48
 49* Authorization decision
 50
 51Two layers decide every request.
 52
 531. *=Dispatch=* (=internal/control/control.go=), in order:
 54   scope gate, read-only gate, disabled account, =admin= noun, pending
 55   account, per-account write budget, stdin gating, handler, audit.
 562. *The handler*, which resolves the repository with =resolveRepo=
 57   (=internal/control/repo.go=) and a predicate from
 58   =internal/policy/access.go=:
 59
 60| Predicate  | True when                                                                |
 61|------------+--------------------------------------------------------------------------|
 62| =CanRead=  | owner; or the repository is public; or a grant of read, write or admin   |
 63| =CanWrite= | owner; or a grant of write or admin                                      |
 64| =CanAdmin= | owner; or a grant of admin                                               |
 65
 66Grants come from =repo_access= rows for users, organizations and teams.
 67Organization-owned repositories have no individual owner; members get
 68access only through grants.
 69
 70Not found versus denied: when the predicate fails, =resolveRepo=
 71checks =CanRead=. If the caller cannot read the repository the answer is
 72"not found", identical to a missing repository. Only a caller who can
 73read it gets "permission denied" for a write. git transport follows the
 74same rule (=internal/sshd/sshd.go=), and so does smart HTTP, which
 75serves public repositories only.
 76
 77Deploy keys bypass the grant model entirely: =runGit= checks only that
 78the key's scope names this repository and allows the operation
 79(=policy.DeployScopeAllows=, =internal/policy/access.go=).
 80
 81* Repository write protections
 82
 83Enforced in the pre-receive hook, so they apply to every credential
 84that reaches git transport (=internal/policy/access.go=,
 85=internal/hookd/hookd.go=):
 86
 87| Setting (=repo settings …=) | Effect                                                                  |
 88|---------------------------+-------------------------------------------------------------------------|
 89| =protect=                 | no deletion, no force push on the branch                                |
 90| =require-mr=              | no direct push to an existing protected branch; only the server's merge writes it |
 91| =protect-tag=             | no deletion or move of matching tags                                    |
 92| =require-signed=          | every incoming commit must verify (OpenPGP or SSHSIG) against a key registered to a verified email |
 93| (always)                  | =refs/merge-requests/*= is server-owned                                 |
 94
 95Merge gates, evaluated by =MergeGates= for =mr merge=, the web merge
 96button and the displayed status (=internal/control/mr.go=):
 97
 98| Gate                  | Satisfied when                                                              |
 99|-----------------------+-----------------------------------------------------------------------------|
100| draft                 | the merge request is not a draft (always on)                                |
101| =require-checks=      | every status on the head is success, and some status exists if CI would have run |
102| =require-approvals N= | N fresh approvals from current writers other than the author, and no active request for changes |
103| =require-codeowners=  | every changed path matching a CODEOWNERS rule has an approval from a listed owner |
104| =require-resolved=    | no unresolved review threads                                                |
105
106* Session security on the web
107
108- CSRF: =SameSite=Lax= withholds the cookie on cross-site posts;
109  =checkOrigin= rejects a post whose =Origin= host differs from the
110  request host (=internal/httpd/accounts.go=).
111- Headers on every response: CSP =default-src 'self'; script-src
112  'none'; style-src 'self' 'unsafe-inline'; img-src * data:;
113  object-src 'none'; base-uri 'none'; form-action 'self';
114  frame-ancestors 'none'=, =X-Frame-Options: DENY=, =nosniff=,
115  =Referrer-Policy: no-referrer=, =Cross-Origin-Opener-Policy:
116  same-origin=, and HSTS for one year with subdomains when TLS is on
117  (=internal/httpd/routes.go=).
118- Destructive web actions (key, email and PGP removal, release, snippet,
119  team and label deletion, user disable and demote) require the target's
120  name typed into the form (=internal/httpd/confirm.go=).
121
122* Rate limits
123
124| Limit                   | Default           | Keyed by              | Code                                  |
125|-------------------------+-------------------+-----------------------+---------------------------------------|
126| SSH auth failures       | 10 per minute     | client IP             | =internal/sshd/ratelimit.go=          |
127| API requests            | 120 per minute    | account, or IP if anonymous | =internal/httpd/apilimit.go=    |
128| API writes              | a tenth of the above | same               | =apilimit.go=                   |
129| Command writes, all surfaces | =limits.write_rate= (60 per minute) | account | =internal/control/control.go= |
130| Login links (web form)  | 5 per hour, plus per-IP | account and IP  | =internal/control/loginlink.go= |
131| Email verification mails| 5 per hour        | account               | =internal/control/register.go=    |
132
133=X-Forwarded-For= is honoured only from addresses listed in
134=http.trusted_proxies= (=internal/httpd/apilimit.go=).