Gå til hovedindhold

Sikkerhedsmodel for hostede runners

fremforges hostede runners kører kundernes Forgejo Actions-workflows på virtuelle T Cloud Public ECS-maskiner pr. commit i regionen EU-DE. Et job i kø, som ikke har en ledig VM til sit commit, får en ny VM med indbygget Docker. Den VM kører kun jobs fra det samme commit i det samme repository, ét ad gangen, venter op til 60 sekunder på det næste og bliver derefter slukket og destrueret. Der er ingen vedvarende runner-pulje, og ingen VM kører nogensinde kode fra to commits, to repositorier eller to kunder.

Denne side beskriver de isolationsgarantier, fremforge giver, de grænser, der gælder pr. tenant, og trusselsmodellen: hvad kundekode kan, hvad den ikke kan, og hvilke designvalg en workflow-forfatter bør træffe for at holde sine secrets sikre.

Hvor workflows kører

KomponentImplementering
BeregningVirtuelle T Cloud Public ECS-maskiner (Elastic Cloud Server), region eu-de, tre availability zones
LivscyklusÉn kortlivet VM pr. commit. Platformen starter en ny ECS-VM, når det første job for et commit sættes i kø, og destruerer den, når commit’ets jobs er færdige. To kunder, og to commits, deler aldrig en kerne, hvilket svarer til GitHub-hostede og GitLab-hostede runners med hensyn til adskillelse mellem tenants. Jobs fra samme commit kan dele VM’en (fx build og derefter deploy), og derfor bør en pipeline behandles som ét tillidsdomæne. Se Trusselsmodel. Kundeworkflows deler aldrig VM med platformens egne workloads. Der er ingen varm pulje (mål 0, af hensyn til omkostninger), så hvert commit betaler de ca. 40 s, det tager at boote ECS.
NetværksplaceringHver VM får sit eget ENI i et dedikeret runner-subnet, med en Security Group pr. VM, der styrer udgående trafik. VM’erne ligger i et dedikeret VPC-subnet fordelt på tre availability zones
VM-kørselÉn VM pr. commit; oprettes, når commit’ets første job sættes i kø, og destrueres, når dets jobs er færdige. Loftet for vægurstid pr. job (60 min som standard, 360 min som absolut loft) håndhæves på runneren
Reaper som sidste værnEn separat reaper uden for selve forløbet sletter forældreløse VM’er: runneren registrerede sig aldrig, runneren afsluttede, men sletningen blev ikke udført, VM’en er ældre end sin slettefrist, eller det absolutte værn på 6 t 10 min.
Runner-binærOpstrøms forgejo-runner (act_runner) v13.2.0, ingen fork. Registrerer sig kortlivet med --ephemeral, så en registrering bruges af én opgave; agenten registrerer sig igen for hver yderligere opgave fra samme commit, og platformen destruerer VM’en, når commit’ets jobs er færdige
VM-imageDet hostede runner-image: debian 13 (trixie) som base, med toolchains bagt ind fra binærer cachet i EU (ingen direkte hentning opstrøms under boot). Imaget er VM’ens boot-disk; Docker-dæmonen er forudinstalleret og kører direkte på VM’en (ingen separat sidecar). Koldstart tager ca. 40 s for hvert commit. Målet for den varme pulje er 0 af hensyn til omkostninger, så ingen VM oprettes på forhånd

Isolationsgarantier

Netværksisolation (pr. VM)

Hver runner-VM får sit eget ENI bundet til en Security Group pr. VM. Som standard afviser SG’en al trafik undtagen:

  • DNS (53/UDP, 53/TCP) til platformens interne DNS-resolver
  • Udgående trafik gennem runnernes egress-proxy, den eneste udgående HTTP-vej

Kundekode kan ikke nå:

  • fremforges api eller dens private database
  • Forgejos interne endpoint (runner-VM’er taler med Forgejo gennem det offentlige apex bag Bunny med et runner-specifikt scope; det interne endpoint kan ikke nås)
  • T Cloud Publics platformtjenester (RDS, DCS, OBS, SFS-Turbo, IAM, KMS)
  • Andre tenants’ runner-VM’er
  • Klyngens metadata-IP (169.254.169.254), som eksplicit blokeres af SSRF-værnet
  • Private RFC1918-intervaller eller loopback

Security Group’en anvendes, når VM’en oprettes, og kan ikke ændres indefra VM’en. VM’en har ingen IAM-agency tilknyttet, så API’erne til netværkskonfiguration kan ikke nås gennem den identitet, der udledes fra metadata.

Udgående trafik

Workflow-trin, der skal bruge det offentlige internet (klone et offentligt repo, hente et værktøj, kalde et SaaS-API), går gennem runnernes egress-proxy via HTTPS_PROXY, som sættes ind i VM’en. Proxyen:

  • Accepterer enhver offentlig unicast-IP inklusive Carrier-Grade NAT (svarer til GitHub-hostede runners og er nødvendigt for T Cloud Publics egne tjenester som SWR, der ligger i CGNAT)
  • Blokerer private RFC1918-adresser, link-local, loopback, multicast, reserverede adresser og eksplicitte cloud-metadata-IP’er (169.254.169.254, T Cloud Publics 100.100.100.200)
  • Logger hvert forsøg på en CONNECT-tunnel med værtsnavn og opslået IP til opbevaring i revisionsøjemed

Proxyen er bevidst tilladende på samme måde som GitHub-hostede runners: kundeworkflows kan nå enhver offentlig vært, de har brug for. Grænsen går ved SSRF og den interne platform, ikke ved tilladelseslister over leverandører.

Pakkecacher (suveræne, i EU)

Før et workflow når det offentlige internet, sendes pakkeinstallationer og hentning af images gennem din organisations egen afhængighedsproxy, som ligger i EU i samme region som runner-VM’erne. Det fælles Docker Hub-spejl og pakkecacherne i VPC’en blev udfaset i oktober 2026. Hver organisations proxy anvender sin egen pakkepolitik, sine egne legitimationsoplysninger opstrøms, sin egen adgangslog og sit eget lagerloft. En offentlig fil, som flere organisationer henter, gemmes én gang, og indhold hentet med en organisations egne legitimationsoplysninger serveres aldrig for en anden. Når pull-through for runners er slået til (standard for alle organisationer, der ikke har gemt deres egne indstillinger for afhængighedsproxyen), peges hvert job mod proxyen, når det starter:

TrafikSættes ind som
npm, pnpm, pip, uvjobbets npmrc, PIP_INDEX_URL, UV_DEFAULT_INDEX
Go, Maven, NuGet, Trivys sårbarhedsdatabase (hver især som angivet i runner_pull_through_ecosystems, alle som standard)GOPROXY, et Maven-spejl af Central, en maskinbred NuGet.Config, TRIVY_DB_REPOSITORY og TRIVY_JAVA_DB_REPOSITORY
Container-images fra ghcr.io, quay.io, mcr.microsoft.com, registry.k8s.io, public.ecr.aws, registry.access.redhat.com; og docker.io, registry.redhat.io, registry.connect.redhat.com kun med organisationens egne legitimationsoplysninger, aldrig for pull requests fra forksVM’ens registry-konfiguration for Docker-dæmonen

Jobbet godkendes over for proxyen med legitimationsoplysninger, der er afgrænset til dets egen organisation og kørsel, så pakkeinstallationer går gennem organisationens pakkepolitik og malwaretjek og vises i dens adgangslog. For container-images på denne vej er proxyen en cache, ikke en gate: en hentning, den afviser, går videre til registryet. Proxyen tjekkes i starten af hvert job. Kan den ikke nås, installerer og henter jobbet fra de offentlige registries gennem egress-proxyen, og reserveløsningen logges. Se Hostede runners.

I det almindelige tilfælde (offentlige afhængigheder) er det gennemsigtigt, så intet i dit workflow ændres.

Bruger du et privat indeks eller registry, så bemærk, at den indsatte værdi er standarden, og derfor går den forud for en standard, der er sat i en committet konfigurationsfil (pip.conf, .npmrc, go env). Peg eksplicit på din private kilde, så den vinder; hvordan du gør det for hvert værktøj, står nedenfor.

Brug ét indeks pr. installation: proxyen eller ét privat indeks, der selv spejler det offentlige registry. Tilføj aldrig et andet indeks ved siden af det første (--extra-index-url, PIP_EXTRA_INDEX_URL, UV_EXTRA_INDEX_URL): installationsværktøjet tager så et pakkenavn fra det indeks, der tilbyder det, og det er sådan, dependency confusion-angreb virker. Se afhængighedsproxyens noter om PyPI.

  • pip: pip install --index-url <your-private> ... (et --index-url på kommandolinjen tilsidesætter den indsatte PIP_INDEX_URL), eller sæt den i workflow-trinnets env:, når det private indeks spejler de offentlige pakker, du har brug for. Vil du installere dine egne pakker fra din organisations registry, mens de offentlige kommer gennem proxyen, så brug to separate installationer med hvert sit ene indeks: først de offentlige afhængigheder, derefter dine pakker med --index-url https://frem.sh/api/packages/<org>/pypi/simple/ --no-deps.
  • uv: samme regel. Fastlås dine egne pakker til dit registry ved navn, med et indeks markeret explicit = true og en [tool.uv.sources]-linje pr. pakke, så intet navn kan slås op i begge.
  • npm: sæt NPM_CONFIG_REGISTRY (og, for pnpm 11, PNPM_CONFIG_REGISTRY) i trinnets env: til ét registry, der spejler det offentlige, eller behold proxyen og send kun dit scope til dit registry (@<your-scope>:registry=…), hvilket fastlåser hvert navn i det scope til ét registry. Se Pakkecacher.
  • Go: sæt GOPRIVATE (fx GOPRIVATE=git.example.com/*), så toolchainen går uden om proxyen og checksum-databasen for dine private modulstier.

Isolation på VM-niveau

FladeHåndhævelse
ProcesWorkflow-trin kører som standard som VM’ens runner-bruger; sudo er tilgængelig, så workflows kan køre apt install, docker, konfigurere systemværktøjer osv. (svarer til forventningerne til GitHub-hostede runners). Grænsen er hypervisoren under VM’en, ikke user namespaces
ArbejdsområdeLokalt kladdeområde på VM’en i /workspace (og /tmp), som ryddes mellem jobs og destrueres sammen med VM’en. Se Jobs, der deler en VM for, hvad der kan følge med mellem jobs fra ét commit
IdentitetVM’en har ingen IAM-agency tilknyttet. Forespørgsler til metadatatjenesten returnerer kun den netværksidentitet, der er nødvendig for at boote; ingen adgang til platformens API’er
RessourcegrænserVM’ens ECS-flavor sætter et hårdt loft for CPU og hukommelse. En løbsk kunde kan ikke opbruge kapacitet ud over VM’ens egne grænser. Løbskhed på tværs af platformen begrænses af loftet for samtidige jobs pr. tenant og platformens beregningskapacitet
Container-runtimeDocker (direkte): dæmonen kører på VM-værten, ikke som sidecar. docker build, docker compose, kind og testcontainers virker direkte uden DinD-kompleksitet. Til workflows, der har brug for ekstra muligheder i container-runtimen, sender labelen dind-privileged jobbet til en flavor med KVM-nesting slået til
HypervisorT Cloud Publics KVM-baserede ECS-hypervisor, den samme VM-grænse mellem kunder, som bruges for alle kommercielle T Cloud Public-kunder, der kører ECS i regionen

Kerneisolation mellem og inden for tenants

Dette er det bærende løfte i modellen for hostede runners. Hver VM er bundet til ét commit i ét repository: en Linux-gæstekerne, der kører på T Cloud Publics hypervisor. Den kører commit’ets jobs ét ad gangen, og platformen destruerer den, når de er færdige. Et job fra et andet commit, et andet repository eller en anden organisation får altid en anden VM med en anden kerne.

MekanismeHvordan
VM pr. commitPlatformen starter en VM til et ventende job, når ingen VM for det commit er ledig. Et udbrud på kerneniveau (fx CVE-2024-1086 nf_tables) kompromitterer kun DEN engangs-VM og jobs fra samme commit, ikke andre tenants
Hurtig nedlukning efter brugEfter sit sidste job venter VM’en op til 60 sekunder på et andet job fra samme commit og bliver derefter slukket og slettet. En separat reaper uden for selve forløbet fanger forældreløse VM’er (runner aldrig registreret, sletning ikke udført) inden for et absolut værn på 6 t 10 min
ENI og SG pr. VMNetværksisolation uafhængigt af kernegrænsen. Tenant A’s VM kan ikke nå tenant B’s VM over netværket: separate ENI’er, separate Security Groups
Livscyklus for ét commitHvert job kører i en ny jobcontainer. En VM genbruges aldrig til et andet commit, repository eller en anden tenant, så den indeholder aldrig en tidligere tenants secrets
OIDC-tokensHver VM får et nyt OIDC-token signeret af runnernes OIDC-udsteder med aud=fremforge og sub=repo:<owner>/<repo>:ref:<refname>. Kundernes tillidspolitikker i deres cloud bruger denne audience og sub til assume_role_with_web_identity

Jobs, der deler en VM

En VM genbruges kun til jobs fra samme commit i samme repository (og dermed samme organisation): andre workflows, kørsler eller genkørsler af det commit, eller en udrulning efter dets build. Mellem de jobs nulstiller fremforge alt, hvad den ved kan indeholde en legitimationsoplysning: jobcontaineren, /workspace og /tmp, Docker-login og npm-konfiguration og de sædvanlige filer med legitimationsoplysninger. Kan nulstillingen ikke bekræftes, tager VM’en ikke flere jobs, og det næste job får en ny VM.

Nulstillingen er ikke det samme som en ny VM. Jobs kan bruge VM’ens Docker-dæmon, som er root på VM’en, så et job kan efterlade noget til et senere job fra samme commit. Images hentet med legitimationsoplysninger, værktøjscachen og Go-buildcachen bliver også på VM’en. Behandl alle jobs fra ét commit som ét tillidsdomæne:

  • Kør ikke kode, du ikke stoler på (testdata fra bidragydere, afhængighedsopdateringer, der ikke er reviewet), i samme commits pipeline som et job, der har produktions-secrets.
  • Beskyt udrulninger med en godkendelse på miljøet (se Opdel jobs i validate og apply), og foretræk kortlivede OIDC-legitimationsoplysninger frem for gemte nøgler.
  • Hvis en udrulning aldrig må dele maskine med et build, så kør den på en BYO-runner.

Grænser pr. tenant

GrænseStandardTilsidesættelse
Samtidige jobs pr. tenant2 pr. bruger garanteret, mindst 10 og højst 60 (prøveperiode: 2). Derover kan en betalende tenant bruge ledig kapacitet op til 60, men kun så længe 20 af platformens job-slots forbliver ledige, så ingen tenants spidsbelastning forsinker en andens garanterede jobs. Til sammenligning, GitHub-hostet: Free 20, Pro 40, Team 60, Enterprise 500Enterprise-on-Demand: en tilsidesættelse pr. tenant erstatter den garanterede andel
Samtidige jobs på tværs af alle tenantsBegrænset af platformens beregningskapacitet i eu-de—
Loft for jobstarter7 jobstarter pr. rullende vindue på 5 minutter pr. job-slot i tenantens loft for spidsbelastning: 420 for en betalende tenant, 14 for en prøveperiode. Det skalerer med et hævet loft for samtidighed, så det er et værn mod misbrug og aldrig den grænse, en normal pipeline rammer først. GitHub offentliggør ingen grænse for jobstarterTilsidesættelse pr. tenant
Vægurstid pr. job3600 s (60 min)Kan konfigureres pr. workflow-trin, men med et loft på 6 t
Pulje af runner-minutterseat_cap × 1000 minutes/month (eller et eksplicit loft pr. tenant)Operatørkonfiguration; svarer til de offentlige priser
VM-flavorc9.large.2 (2 vCPU / 4 GiB RAM) som standard; c9.xlarge.4 (4 vCPU / 16 GiB) for runs-on: [fremforge, large]Labelen i workflow-trinnets runs-on: sender jobbet til andre flavors

Når en tenant overskrider puljen af runner-minutter, og merforbrug ikke er slået til (eller er frosset), afviser platformen at sende jobbet ud: workflowets job i kø skifter til Cancelled i Forgejo. Tenants med merforbrug slået til kører videre og faktureres for det ekstra.

Når en tenant rammer loftet for samtidige jobs, venter yderligere jobs i kø og sendes ud, så snart et job-slot bliver ledigt.

Hvad kundekode IKKE kan

  • Nå fremforges api, Forgejos interne endpoint eller nogen anden platformtjeneste (api’et er kun tilgængeligt gennem det offentlige Bunny-apex med korrekt godkendelse, ligesom for enhver ekstern kalder)
  • Læse en anden tenants runner-VM (separat ENI; trafik mellem VM’er afvises eksplicit af Security Group’en)
  • Læse en anden tenants secrets (workflow-secrets sendes kun til den runner, der kører det workflow, de tilhører; når VM’en destrueres, slettes legitimationsoplysningerne i hukommelsen)
  • Bevare tilstand ud over sit eget commit (VM’en destrueres, når commit’ets jobs er færdige, og ingen disk deles mellem VM’er)
  • Nå OTC-API’et direkte med privilegeret scope (ingen IAM-agency tilknyttet; identiteten fra metadata har ingen roller)
  • Nå cloud-metadata-endpoints (169.254.169.254, 100.100.100.200, begge afvist eksplicit af SSRF-værnet)
  • Opbruge runner-puljen (loft for samtidige jobs pr. tenant og platformens samlede beregningsloft)
  • Udsulte køen for andre tenants (loft for samtidige jobs pr. tenant)
  • Bryde ud af hypervisoren (VM’en er grænsen; værten under den tilhører T Cloud Public og er isoleret af den samme VMM, der adskiller alle kommercielle ECS-tenants i regionen)

Anbefalede workflow-strukturer

Opdel jobs i validate og apply

Kør kode, du ikke stoler på (PR-builds, afhængighedsscanninger, testdata fra bidragydere), i et validate-job uden produktionslegitimationsoplysninger, og beskyt apply-jobbet med en godkendelse på miljøet. Validate-jobbet ser aldrig apply-jobbets secrets. Begge jobs hører dog til samme commit, så de kan køre på samme VM efter hinanden: se Jobs, der deler en VM.

Fastlås uses:-referencer med SHA, når forsyningskæden betyder noget

uses: actions/checkout@v4 slås op som frem.sh/actions/checkout (fremforges native Forgejo pull-spejl af github.com/actions/checkout, synkroniseret hver 8. time). Spejlet følger tags opstrøms, inklusive force-pushes, så at fastlåse med SHA er den eneste måde at garantere byte-for-byte stabilitet på:

# Bytewise-stable across maintainer force-pushes
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5  # v4.2.0

# Picks up upstream's current HEAD at last mirror sync — including
# any maintainer-credential-compromise force-push
- uses: actions/checkout@v4

Brug OIDC til cloud-godkendelse, ikke langlivede secrets

Hver runner-VM får et nyt OIDC-token med aud=fremforge. Konfigurér din cloududbyders tillidspolitik til at acceptere dette token og bytte det til kortlivede legitimationsoplysninger. Undgå at gemme langlivede AWS-adgangsnøgler eller JSON-filer til GCP-servicekonti i workflow-secrets, hvor de ender i processens miljø.

Sæt frister på workflow-trin

Sæt timeout-minutes: på de enkelte trin for at begrænse, hvor længe et trin, der hænger, kan køre. Loftet for vægurstid på VM-niveau (activeDeadlineSeconds=3600) er platformens bund; timeouts på trinniveau giver hurtigere feedback.

Overblik over trusselsmodellen

TrusselAfbødning
Et kundeworkflow indeholder en legitimationsoplysningPush-beskyttelse (Gitleaks) blokerer commit’et, før det lander i historikken
Et kundeworkflow forsøger at læse fremforge-platformenENI og SG pr. VM afviser; ingen IAM-agency tilknyttet VM’en; api’et kan kun nås via den offentligt godkendte vej
En tenants job forsøger at læse en andens VMENI pr. VM på separate IP’er; SG afviser som standard på tværs af runner-subnettet; og de er på forskellige VM’er med forskellige kerner
Udbrud på kerneniveau (root i VM’en → værtens kerne via en CVE)Kompromitterer kun den engangs-VM, der kører det commits jobs. Andre tenants gør IKKE: de er på deres egen VM. VM’en destrueres under alle omstændigheder ca. et minut efter commit’ets sidste job
Et kundeworkflow forsøger at lække data via DNSDNS er tilladt (workflows har reelt brug for det), men egress-proxyen logger hver CONNECT med fuldt værtsnavn til revision
Kompromitteret act_runner opstrømsBinær testet opstrøms (vi forker ikke). Spejlet gennem EU-cachen med Trivy-gates på runner-imaget. CVE-rettelser kommer med den ugentlige genopbygning
Et kundeworkflow forsøger at nå metadata-IP’erEksplicit afvisning i SSRF-værnet: 169.254.169.254 (AWS-stil) og 100.100.100.200 (T Cloud Public)
En kunde monopoliserer kapacitetenGaranteret andel pr. tenant (2 pr. bruger, 10 til 60) med spidsbelastning kun ind i ledig kapacitet, der efterlader 20 job-slots fri, loft for jobstarter (7 starter pr. job-slot i spidsbelastningsloftet pr. 5 min), loft over puljens minutter
En forældet runner-VM overlever sin opgaveLoftet for vægurstid pr. job dræber hårdt ved fristen; platformen sletter VM’en, når runneren afslutter; en separat reaper uden for selve forløbet fanger forældreløse VM’er med et absolut værn på 6 t 10 min

Sporbarhed

Hver udsendelse af et job logges tre steder:

  1. Tabellen runner_jobs: tenant_id, repo, forgejo_task_id, started_at, finished_at, duration_seconds, vm_id. Kilde til aggregering af fakturering og fanen Infrastructure i operatørkonsollen
  2. Tabellen metering_events: rækker med code=runner_minute udledt af runner_jobs.duration_seconds. Driver fakturalinjerne og tenantens panel “Runner minutes used”
  3. Operatørens logstrøm: strukturerede logs over oprettelser til operatøren, med løbende alarmer ved afviste udsendelser, VM’er, der sidder fast, overskredne frister og opbrugt kapacitet

Tenants ser deres eget forbrug under Administration → Byg og udrul → CI-forbrug, inklusive et antal minutter pr. måned og en tabel med varighed pr. job.