Commit e977337c14
Verified · cmc
Layout: unified · split
Admin.org +29 −7
| @@ -75,6 +75,12 @@ validation still prints, followed by the contradiction. | |||
| 75 | - =files=: =cert_file= + =key_file=. | 75 | - =files=: =cert_file= + =key_file=. |
| 76 | - =off=: plain HTTP — development, or behind a TLS-terminating proxy. | 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 | ** [web] | 84 | ** [web] |
| 79 | - =mode= — =view_only= (default) | =accounts=. In view_only the mutating | 85 | - =mode= — =view_only= (default) | =accounts=. In view_only the mutating |
| 80 | web routes are never registered; in accounts, browser sessions are | 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 | * CI runner | 309 | * CI runner |
| 304 | 310 | ||
| 305 | =gitbay-runner= executes builds queued by pushes. It polls over SSH as | 311 | =gitbay-runner= executes builds queued by pushes and merge requests. It |
| 306 | an admin account (runner commands are admin-only: a runner executes | 312 | polls over SSH with a key added by =keys add --scope runner=, which |
| 307 | arbitrary repo code), clones, runs the steps, streams the log back, and | 313 | reaches only the runner protocol and read-only git (a runner executes |
| 308 | resolves the commit status. Run it as a dedicated unprivileged user: | 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 | #+begin_src sh | 320 | #+begin_src sh |
| 311 | useradd --system --create-home --home-dir /var/lib/gitbay-runner ci-runner | 321 | useradd --system --create-home --home-dir /var/lib/gitbay-runner ci-runner |
| 312 | sudo -u ci-runner ssh-keygen -t ed25519 -N "" -f /var/lib/gitbay-runner/.ssh/id_ed25519 | 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 \ | 323 | ssh-keygen -t ed25519 -N "" -f /tmp/ci-bootstrap |
| 314 | --key /var/lib/gitbay-runner/.ssh/id_ed25519.pub | 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 | gitbay-runner -remote git@127.0.0.1 -workdir /var/lib/gitbay-runner/work | 328 | gitbay-runner -remote git@127.0.0.1 -workdir /var/lib/gitbay-runner/work |
| 316 | #+end_src | 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 | =make deploy-runner= also installs | 337 | =make deploy-runner= also installs |
| 319 | =deploy/gitbay-runner.override.conf= as a systemd drop-in: =Nice=10=, | 338 | =deploy/gitbay-runner.override.conf= as a systemd drop-in: =Nice=10=, |
| 320 | =CPUWeight=30=, =IOWeight=30=, so a build never starves the host's sshd, | 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 | daemon instances; without the drop-in a deploy's copy over the admin | 344 | daemon instances; without the drop-in a deploy's copy over the admin |
| 323 | sshd stalled. Both deploy targets copy with =rsync --partial=, which | 345 | sshd stalled. Both deploy targets copy with =rsync --partial=, which |
| 324 | resumes a stalled transfer. | 346 | resumes a stalled transfer. |
Threat-Model.org +33
| @@ -104,6 +104,36 @@ DENY=, =X-Content-Type-Options: nosniff=, =Referrer-Policy: no-referrer=, | |||
| 104 | and =Strict-Transport-Security= when TLS is on. The UI needs no | 104 | and =Strict-Transport-Security= when TLS is on. The UI needs no |
| 105 | JavaScript, so =script-src 'none'= costs nothing. | 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 | * Residual risks, accepted | 137 | * Residual risks, accepted |
| 108 | 138 | ||
| 109 | - External images in rendered READMEs and profile about text load from | 139 | - External images in rendered READMEs and profile about text load from |
| @@ -116,3 +146,6 @@ JavaScript, so =script-src 'none'= costs nothing. | |||
| 116 | harmless (see [[Admin]]). | 146 | harmless (see [[Admin]]). |
| 117 | - A global signature-verification epoch over-invalidates the cache on any | 147 | - A global signature-verification epoch over-invalidates the cache on any |
| 118 | trust-input change. Correct, not a leak; a performance tradeoff. | 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. | ||