Commit e977337c14
Verified · cmc
Layout: unified · split
Admin.org +29 −7
| @@ -75,6 +75,12 @@ validation still prints, followed by the contradiction. | ||
| 75 | 75 | - =files=: =cert_file= + =key_file=. |
| 76 | 76 | - =off=: plain HTTP — development, or behind a TLS-terminating proxy. |
| 77 | 77 | |
| 78 | =trusted_proxies= lists the addresses or CIDRs of reverse proxies in | |
| 79 | front of the daemon. A request from one of them is attributed, for API | |
| 80 | rate limiting, to the last =X-Forwarded-For= hop that is not itself a | |
| 81 | trusted proxy; from anyone else the header is ignored. Empty, the | |
| 82 | default, is right when gitbayd terminates TLS itself. | |
| 83 | ||
| 78 | 84 | ** [web] |
| 79 | 85 | - =mode= — =view_only= (default) | =accounts=. In view_only the mutating |
| 80 | 86 | web routes are never registered; in accounts, browser sessions are |
| @@ -302,23 +308,39 @@ startup to point at the current binary path. | ||
| 302 | 308 | |
| 303 | 309 | * CI runner |
| 304 | 310 | |
| 305 | =gitbay-runner= executes builds queued by pushes. It polls over SSH as | |
| 306 | an admin account (runner commands are admin-only: a runner executes | |
| 307 | arbitrary repo code), clones, runs the steps, streams the log back, and | |
| 308 | resolves the commit status. Run it as a dedicated unprivileged user: | |
| 311 | =gitbay-runner= executes builds queued by pushes and merge requests. It | |
| 312 | polls over SSH with a key added by =keys add --scope runner=, which | |
| 313 | reaches only the runner protocol and read-only git (a runner executes | |
| 314 | arbitrary repository code, so the key it holds must not do more), then | |
| 315 | clones, runs the steps, streams the log back and resolves the commit | |
| 316 | status. Run it as a dedicated unprivileged user on a non-admin account. | |
| 317 | =admin user create --key= registers a full-scope key, so the runner key | |
| 318 | is added afterwards through a bootstrap key that is then removed: | |
| 309 | 319 | |
| 310 | 320 | #+begin_src sh |
| 311 | 321 | useradd --system --create-home --home-dir /var/lib/gitbay-runner ci-runner |
| 312 | 322 | sudo -u ci-runner ssh-keygen -t ed25519 -N "" -f /var/lib/gitbay-runner/.ssh/id_ed25519 |
| 313 | gitbayd --config /etc/gitbay/config.toml admin user create ci --admin \ | |
| 314 | --key /var/lib/gitbay-runner/.ssh/id_ed25519.pub | |
| 323 | ssh-keygen -t ed25519 -N "" -f /tmp/ci-bootstrap | |
| 324 | gitbayd --config /etc/gitbay/config.toml admin user create ci --key /tmp/ci-bootstrap.pub | |
| 325 | ssh -i /tmp/ci-bootstrap git@127.0.0.1 keys add --scope runner < /var/lib/gitbay-runner/.ssh/id_ed25519.pub | |
| 326 | ssh -i /tmp/ci-bootstrap git@127.0.0.1 keys remove "$(ssh-keygen -lf /tmp/ci-bootstrap.pub | awk '{print $2}')" | |
| 327 | rm /tmp/ci-bootstrap /tmp/ci-bootstrap.pub | |
| 315 | 328 | gitbay-runner -remote git@127.0.0.1 -workdir /var/lib/gitbay-runner/work |
| 316 | 329 | #+end_src |
| 317 | 330 | |
| 331 | =admin runners= shows which account each runner polls as; an admin key | |
| 332 | still works for the protocol during a rotation. A merge request head | |
| 333 | from a fork is built in the target repository as untrusted: the claim | |
| 334 | carries no secrets. Same-repository heads were built by their branch | |
| 335 | push and are not built again. | |
| 336 | ||
| 318 | 337 | =make deploy-runner= also installs |
| 319 | 338 | =deploy/gitbay-runner.override.conf= as a systemd drop-in: =Nice=10=, |
| 320 | 339 | =CPUWeight=30=, =IOWeight=30=, so a build never starves the host's sshd, |
| 321 | the daemon or the backup timers. The e2e suite alone starts sixty | |
| 340 | the daemon or the backup timers, and =NoNewPrivileges=, | |
| 341 | =ProtectSystem=full=, =ProtectKernelTunables=, =ProtectControlGroups= | |
| 342 | and =RestrictSUIDSGID=, so a step cannot reach outside its workspace | |
| 343 | and the runner's home. The e2e suite alone starts sixty | |
| 322 | 344 | daemon instances; without the drop-in a deploy's copy over the admin |
| 323 | 345 | sshd stalled. Both deploy targets copy with =rsync --partial=, which |
| 324 | 346 | resumes a stalled transfer. |
Threat-Model.org +33
| @@ -104,6 +104,36 @@ DENY=, =X-Content-Type-Options: nosniff=, =Referrer-Policy: no-referrer=, | ||
| 104 | 104 | and =Strict-Transport-Security= when TLS is on. The UI needs no |
| 105 | 105 | JavaScript, so =script-src 'none'= costs nothing. |
| 106 | 106 | |
| 107 | * The CI runner | |
| 108 | ||
| 109 | =gitbay-runner= is the one component that executes repository content. | |
| 110 | gitbayd never does: it reads =.gitbay/ci.yml= and queues a build, and a | |
| 111 | runner, polling over SSH, clones the commit and runs its steps. | |
| 112 | ||
| 113 | - *What the runner holds.* A key added with =keys add --scope runner=, | |
| 114 | which the dispatcher confines to =runner next=, =runner log= and | |
| 115 | =runner done= and to read-only git. A step that reads the key off the | |
| 116 | disk gets exactly that: it cannot administer the instance, push, or | |
| 117 | read a repository the runner's account cannot. An admin key still | |
| 118 | works for the runner protocol so an operator can rotate at their own | |
| 119 | pace; a runner host should not hold one. | |
| 120 | - *What a build sees.* The commit, the =GITBAY_*= variables and the | |
| 121 | repository's secrets — unless the head came from another repository. | |
| 122 | A merge request from a fork is built in the target as untrusted, with | |
| 123 | no secrets, so a stranger's branch cannot read the target's deploy | |
| 124 | credentials. | |
| 125 | - *Where it runs.* Steps run as the runner's own user on the runner | |
| 126 | host, with no container; the systemd drop-in adds =NoNewPrivileges=, | |
| 127 | =ProtectSystem=full= and the kernel and cgroup protections. =-repos= | |
| 128 | limits a runner to named repositories, which is the control that | |
| 129 | matters on an open instance: without it a runner builds whatever | |
| 130 | anyone pushes. | |
| 131 | ||
| 132 | Anything a step can do as the runner's user, a pushed =ci.yml= can do. | |
| 133 | Treat the runner host as executing untrusted code: keep it off the | |
| 134 | daemon's host where the database lives, or scope it to repositories | |
| 135 | whose writers you trust. | |
| 136 | ||
| 107 | 137 | * Residual risks, accepted |
| 108 | 138 | |
| 109 | 139 | - External images in rendered READMEs and profile about text load from |
| @@ -116,3 +146,6 @@ JavaScript, so =script-src 'none'= costs nothing. | ||
| 116 | 146 | harmless (see [[Admin]]). |
| 117 | 147 | - A global signature-verification epoch over-invalidates the cache on any |
| 118 | 148 | trust-input change. Correct, not a leak; a performance tradeoff. |
| 149 | - Build steps run as the runner's user with no container. Isolation is | |
| 150 | the key scope, the sandboxing drop-in and =-repos=; containers are | |
| 151 | future work. | |