.gitbay/wiki/Architecture/05-Identity-and-Access.org
134 lines · 9872 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, SSH key, operation, expiry | not stored (stateless) | one repository, upload or download | 1 h | with its key: refused once the key is removed or expires, its account is disabled, or it loses the access the operation needs |
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=).