feat(preview): serve each pull request's client at pr-N.<domain> #10
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/pr-previews"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #9
Changes
New
docs/preview/, run by the demo's user next to the demo:sync.sh(host, systemd timer every 2 minutes): runs the fetch container, unpacks each new build through the unpack container intodata/.new-N, checks again that it holds only plain files and directories and anindex.html, adds the preview'sconfig.json,_preview.jsonand.source, and swaps it in fordata/pr-N. Removes previews of closed pull requests (or beyondPREVIEW_MAX), then re-creates nginx with oneHost(pr-N.…)per preview, or removes it when there are none. If the pull request listing fails, it stops before removing anything.fetch.sh(container: Alpine + jq, read-only root, no capabilities; readsdata/, writes onlydata/.incoming): lists open pull requests intomain(optionally onlyPREVIEW_AUTHORS), finds the newest successfulci.ymlrun of each head commit and itsclient-distartifact, and downloads it when the commit, run, artifact or upload time changed (a re-run replaces the artifact in place under the same ids). Refuses artifacts overPREVIEW_MAX_ZIP_MB.unpack.sh(container: no network, read-only root, no capabilities, 256 MB tmpfs): unzips into the tmpfs, refuses anything but plain files and directories or a build withoutindex.html, normalizes permissions and copies into/out, the only other thing it can see.nginx.conf.template: servespr-Nread-only with the client's caching rules,disable_symlinks, no dotfiles, and headers the build cannot change: a CSP (connect-src 'self' <homeserver>,script-src 'self' 'wasm-unsafe-eval',worker-src 'self' blob:,form-action 'self',frame-ancestors 'none', …),X-Robots-Tag: noindex,Referrer-Policy: no-referrer.compose.yaml(projectkoto-preview),Dockerfile,.env.example, and aREADME.mdwith the design, security notes, setup (DNS, front proxy,.env, systemd units) and limits.ci.yml's shell syntax check coversdocs/preview/*.sh;demo.mdanddevelopment.mdlink the new guide.Deviation from #9: the archive is downloaded in one container and unpacked in a second, network-less one that sees only that archive and the preview's own new directory, so even an unzip that followed a hostile symlink or path could only spoil that preview.
Verification
Docker is not available where this was written, so the scripts were run with a stand-in for
docker composethat executes the container scripts directly, using the same busyboxwget/unzipas Alpine, and jq 1.8.1:state=all, since no pull request is open): downloaded #8'sclient-dist(artifact 5) without a token, unpacked it, wroteconfig.jsonfor the demo homeserver and_preview.json; the rule came out asHost(`pr-8.preview.genji1024.com`); a plantedpr-99was removed; a second run changed nothing.unpack.sh: a symlink entry and a symlink-then-file entry are refused;../../xand/absare stripped by busybox and land inside the output directory; an archive withoutindex.htmlis refused.sh -npasses for every script.Not verified yet, to check on the host: nginx's config and headers, the client working under the CSP (wasm crypto, the image-compression worker falling back to the main thread), rootless Docker file ownership, and the tmpfs limit against a zip bomb.
LGTM!