Repository navigation
feat(regulations): deploy regulations-collab and latex-worker - #32
Draft
lorenzocorallo wants to merge 4 commits into
Draft
lorenzocorallo wants to merge 4 commits into
lorenzocorallo wants to merge 4 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. CodeRabbit Verify
|
regulations-collab runs the backend image with `bun run collab.js` in the backend namespace, so it always ships with the backend's digest. Only its websocket (4100) is published, on collab.polinetwork.org; its internal API (4101) admits the backend only, and it may reach only DNS, PostgreSQL and the worker. latex-worker runs untrusted LaTeX: no egress at all, ingress from the backend and collab only, PSA restricted, read-only root, tmpfs /work and /tmp with size limits, and a LimitRange plus ResourceQuota sized from real RRI compiles so it cannot starve the 2-vCPU node. The backend gets the regulations env (optional secrets in a separate ExternalSecret so missing entries never block it). No GitHub webhook is published: the backend polls while an emanation runs, so the API stays in-cluster only (#28).
lorenzocorallo
force-pushed
the
feat/regulations-services
branch
from
November 2, 2026 19:44
cdf727a to
deef6c5
Compare
…twork.org The GitHub App webhook (POST /public/webhooks/github) becomes the main emanation trigger; the backend's polling stays as slow reconciliation. One IngressRoute is the backend's only public surface. It matches Host(api.polinetwork.org) && PathPrefix(/public/) and a whitelist PathRegexp, so dots, encoded characters and empty segments never route and /public/../api or /public/%2e%2e/api cannot reach another backend path. Middlewares cap the body at 1 MiB and rate-limit per visitor (Cf-Connecting-Ip) and in total. Traefik is added to the backend's allow-callers policy; the route is the path guard, and the tests assert it is the only router targeting backend-service, with exactly that rule. REGULATIONS_GITHUB_WEBHOOK_SECRET comes from the regulations ExternalSecret.
…he worker's uid latex-worker: - run as uid 10200, used by nothing else. The worker caps each xelatex with prlimit --nproc, and RLIMIT_NPROC counts every task of the uid on the node: as uid 1000, shared with the Bun and Node apps, compiles failed once the node had ~90 uid-1000 tasks (xelatex could not fork xdvipdfmx). Tested on k3s with the real worker image: fails as 1000, passes as 10200. - use the worker's own env names (WORKER_PORT, WORKER_WORK_DIR, WORKER_TMP_DIR) and set WORKER_MEMORY_LIMIT_MB and WORKER_MAX_WORKDIRS. - probe GET /health. - drop the Pod-level kpathsea/HOME variables: the worker runs xelatex in a clean environment it sets up itself. regulations-collab: REGULATIONS_INTERNAL_SECRET is required (collab refuses to start without it), explicit ports, and REGULATIONS_COMPILE_CONCURRENCY=2 to match the worker. backend: REGULATIONS_GITHUB_MODE=app and REGULATIONS_GITHUB_BASE=main. Tests: the worker's uid is unique, its work and tmp dirs are tmpfs mounts, and collab never sends more parallel compiles than the worker runs.
Bun resolves '..' and '%2e%2e' before the backend sees the path, so the backend can't tell an escape from the path alone. The backend-public route now sets X-PoliNetwork-Ingress: public (Traefik overwrites any client value), and the backend lets stamped requests reach only its declared /public routes. Tested on k3s with Traefik 3.7.8: spoofed, lowercase, duplicate, mixed-case and empty client values all arrive as exactly one 'public'; in-cluster callers get no marker.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deployment side of the regulations editor (shared spec, decision 4 and the "Deployment" contract). Aligned with the final backend services in PoliNetworkOrg/backend#110–#117 (image, entrypoint, ports, env names), and verified with the real images built from #117 (
d8429f9, re-verified oneca606e).Do not merge yet. Merge order and prerequisites are at the bottom.
What changes
regulations-collab(Hocuspocus), in thebackendnamespacebun run collab.js. It sits in the same Flux Kustomization, so it always runs the backend's digest; no new image entry is needed.collab.polinetwork.org(proposed name), and Traefik passes the Upgrade through. No Cloudflare Access: the single-use ticket authenticates each connection.Recreate, because Hocuspocus keeps open documents in memory.REGULATIONS_INTERNAL_SECRETis a required ref, since collab refuses to start without it.COLLAB_PORT/COLLAB_INTERNAL_PORTare set explicitly.REGULATIONS_COMPILE_CONCURRENCY=2, matching the worker's 2 (the default is 3, which would only queue at the worker).bunuser), read-only root, tmpfs/tmp, no capabilities, seccomp RuntimeDefault, no service account token.latex-worker, in its own namespaceghcr.io/polinetworkorg/latex-worker, with a newlatex-worker-imageprovider and Receiver entry, following the backend image pattern.restrictedis enforced on the namespace, plus read-only root, no capabilities, no SA token.prlimit --nproc=64, andRLIMIT_NPROCcounts every task of that uid on the node. As uid 1000, which the Bun and Node apps share, every compile failed once the node had about 90 uid-1000 tasks: xelatex couldn't forkxdvipdfmx("I can't write on file `RRI.pdf'")./workis 512Mi (the warm workdirs) and/tmpis 64Mi.WORKER_PORT,WORKER_WORK_DIR,WORKER_TMP_DIR,WORKER_SECRET(required: an empty secret would accept any caller),WORKER_MEMORY_LIMIT_MB=1024(the per-xelatexprlimit --as), andWORKER_MAX_WORKDIRS=40.openin_any/openout_any, caches in/tmp.GET /health, which needs no auth.WORKER_CONCURRENCY=2andWORKER_TIMEOUT_MS=45000./worktmpfs, since memory-backed volumes count against it.Backend
REGULATIONS_*env, includingREGULATIONS_GITHUB_MODE=appandREGULATIONS_GITHUB_BASE=main. The secrets live in a separateregulationsExternalSecret and the backend reads them withoptional: true, so a missing entry never blocks the backend./publicprefix (spec, "Public backend routes"). GitHub posts toPOST https://api.polinetwork.org/public/webhooks/github, and the webhook is now the main emanation trigger. The backend's polling stays only as slow reconciliation for missed deliveries.api.polinetwork.org, the backend's public host before feat(backend): close public access and allow only in-cluster callers #28. Its DNS already points at the tunnel.apps/backend/public-route.yaml) is the backend's only public surface. Its rule isHost(`api.polinetwork.org`) && PathPrefix(`/public/`) && PathRegexp(`^/public(/[A-Za-z0-9_-]+)+$`).-and_match. Dots,%(any encoded character),;,\, empty segments and a trailing slash never match.buffering, 413 above it);Cf-Connecting-Ip(the TCP peer is always cloudflared);X-PoliNetwork-Ingress: publicstamped on every request (Traefik overwrites any client value). Bun resolves..and%2e%2ebefore the app sees the path, so the backend uses this marker as its second layer: a stamped request may only reach a declared/publicroute, whatever its path became. In-cluster callers never carry it.allow-callerson 3000. A NetworkPolicy can't filter paths, so the route is the guard, and the tests make it the only router that can targetbackend-service.Tests
tests/workload-manifests.test.rbnow asserts the sandbox and exposure properties:backend-service: the/publicIngressRoute, with exactly that rule, a body limit, both rate limits and theX-PoliNetwork-Ingress: publicmarker;/public/webhooks/githuband rejects/api/trpc,/internal/events,/public,/publicx,/PUBLIC/...,/public/../api/trpc,/public/%2e%2e/api/trpc,/public/..%2fapi,/public%2f..%2fapi,//public/a,/public//a,/public/a/, NUL,;and backslashes.Also: the worker's uid is used by no other workload;
WORKER_WORK_DIR/WORKER_TMP_DIRare size-limited tmpfs mounts, not the read-only root; and collab never sends more parallel compiles than the worker runs.Each assertion was mutation-checked: I broke the manifest and confirmed the test fails.
Verification
kustomize build(v5.7.1, as in CI) passes forclusters/k3s, everyapps/*and the touched infrastructure. Both Ruby test files pass.kubeconform -strictwith the CRD catalog validates all 62 rendered resources, including ExternalSecret, ResourceSet, ResourceSetInputProvider, Receiver, IngressRoute and Middleware. None were skipped.d8429f9).legal-texliveis private, so the worker used a Debian TeX Live base. They ran under the rendered manifests next to a test Postgres. The backend's IdP, OAuth and Azure env were stripped so the test never contacted production.0033completed, with the expected warnings (no GitHub App, no LLM)./health200 and backend → worker/health200. collab → worker 200./assets/<hash>, POST/compilewith the Bearer secret):eca606e: a real backend, collab and worker ran with Postgres and Redis under feat(regulations): deploy regulations-collab and latex-worker #32's manifests, and the/publicwebhook was exercised through Traefik:{"duplicate":true};/public/...route gets 404 from the backend;?/../../api/trpcstays on the webhook handler;/public/../api,%2e%2e,%2fand a trailing slash get 404 at Traefik.X-PoliNetwork-Ingress: publicto/api/trpcgets 404 from the backend; without it, normal behaviour (401 without a token).RLIMIT_NPROC("Max processes: unlimited") and runs as 10200. With 94 uid-1000 tasks on the node, the RRI compiled cold in 1.4 s and warm in 0.43 s, and 3 parallel compiles were all OK.bun run collab.js→ ws echo on 4100 and HTTP on 4101.NetworkPolicy matrix, enforced by kube-router:
Sandbox: uid 1000,
/etcread-only,/workand/tmparenoswaptmpfs of 512M and 64M, CapEff 0, NoNewPrivs 1, seccomp 2, no SA token. PSA rejects a privileged root Pod inlatex-worker.The quota is fully used by one worker, and a
Recreaterollout still succeeds under it.The backend Deployment starts with the
regulationsSecret absent, which confirms the optional refs work.Real RRI compile (this repo's
latex/RRI.tex, 19 pages, fonts via relativePath=) inside the worker Pod under the exact securityContext and limits:latexmklatexmkThe spec's ~40-page document should roughly double these times.
Websocket through Traefik via the Ingress (
Host: collab.polinetwork.org): upgrade 101. An active connection and a fully idle one were both held for 150 s with no disconnect, so Traefik 3.7.8's default timeouts don't cut upgraded connections. Any other host returns 404./publicroute on real Traefik 3.7.8, with raw request lines sent byte-for-byte asHost: api.polinetwork.orgto a stand-in backend that echoes what it receives:/public/webhooks/github(any method), and/public/a-b_c/D9./,/api/trpc,/api/trpc/regulations.list,/internal/events,/trpc,/public,/public/,/publicx,/PUBLIC/...,/x/public/a,/public/webhooks/github/;/public/../api/trpc,/public/webhooks/../../api/trpc;/public/%2e%2e/api/trpc,/public/%2E%2E/...,/public/..%2fapi%2ftrpc,/public/%2e%2e%2f...,/public%2f..%2fapi%2ftrpc,/public/webhooks%2fgithub,/public/a%00,/public/a;x,/public/a%5c..%5capi,/public/a\\..\\api;http://api.polinetwork.org/api/trpc;/public/webhooks/githubonbackend.oradmin.polinetwork.org./public:/public/./webhooks/github,/public//webhooks/githuband//public/webhooks/githubarrive as/public/webhooks/github;/public/%61piarrives as/public/api.?/../../api/trpc. The backend must route on the pathname only.X-PoliNetwork-Ingress: publicwhether the client sends nothing, a spoofed value, lowercase, duplicates, mixed case or an empty value. A direct in-cluster call gets no marker.The real
ghcr.io/polinetworkorg/backend:latestrunsbun1.4.4 as uid 1000 (bun) under a read-only root with only/tmpwritable.Not verified
collab.jsandlatex-workerimages don't exist yet. Their probes are TCP, as for the backend./public: the Access bypass below, edge URL normalization, and GitHub's deliveries actually arriving.kv-pn-appscondition gainslatex-worker.Before merging
Images:
legal-texlive(private, underlegal) must be multi-arch, including arm64, and must grant read access toPoliNetworkOrg/backend's Actions (package settings → Manage Actions access). Otherwise the backend'slatex-workerjob can't build.latex-workerpackage to public. The cluster and Flux pullghcr.io/polinetworkorg/*without credentials. Its layers are only TeX Live and the worker.Merge and release the backend PRs that add
collab.jsand publishghcr.io/polinetworkorg/latex-worker. The collab Deployment uses the backend image, so merging earlier would crash-loop it and fail the backend Kustomization's health check. The worker stays inert until its image exists.Create these in
kv-pn-apps:regulations-internal-secretopenssl rand -hex 32regulations-worker-secretopenssl rand -hex 32(required by the worker)regulations-github-app-private-keyaz keyvault secret set --file)regulations-github-webhook-secretopenssl rand -hex 32, set the same value in the App)regulations-llm-api-keyEvery entry must exist, or its ExternalSecret stays not-Ready. Use a placeholder until the App or LLM key exists.
Cloudflare Zero Trust:
collab.polinetwork.orgto thek3s01tunnel → Traefik, like the other hostnames. Keep WebSockets enabled on the zone and don't put Access on it.api.polinetwork.orgis currently behind an Access application (it answers 302 to the Access login). Add a more specific Access application forapi.polinetwork.org/public/with a Bypass / Everyone policy so GitHub can reach it, and keep the existing Access app on the rest of the host as an extra layer at the edge.api.polinetwork.org→ Traefik still exists.Fill in the TODO values in
apps/backend/deployment.yaml(plain env, not secret):REGULATIONS_GITHUB_APP_ID,REGULATIONS_LLM_PROVIDER/MODEL,REGULATIONS_TELEGRAM_CHAT_ID/TOPIC_ID, and confirmREGULATIONS_EMAIL_SENDER. Empty values mean that feature is off.Backend: #117 now serves
POST /public/webhooks/githubwith the contract's checks: HMAC, replay protection by delivery id, 1 MiB body limit, its own rate limits, and 404 while the secret is unset. Polling is reconciliation everyREGULATIONS_RECONCILE_SECONDS(default 300). Migration0034_webhook_deliveriescomes with it.GitHub App webhook: URL
https://api.polinetwork.org/public/webhooks/github, content typeapplication/json, the secret above, and events Pull request, Check suite and Release.Follow-ups (not in this PR)
podPidsLimit(k3skubelet-arg: pod-max-pids=…, inansible/roles/k3s) would bound any Pod's processes. It affects every workload, so it's left for a separate change.