Gå til hovedinnhold

Uforanderlige utgivelser og utgivelsesattestering

En publisert utgivelse er et løfte: disse filene, bygd fra denne commiten, under denne taggen. Uten uforanderlighet kan løftet stille brytes i ettertid: en tagg som flyttes til en annen commit, en ressurs som byttes ut med en annen binærfil, en utgivelse som slettes og opprettes på nytt under samme navn. Uforanderlige utgivelser gjør hver av disse umulig, og en utgivelsesattestering lar hvem som helst sjekke, på sin egen maskin, at det de lastet ned, er det som ble publisert.

Hva som fryses

Når uforanderlige utgivelser er slått på, fryses en utgivelse i det øyeblikket den publiseres. Fra da av:

EndringTillatt?
Endre tittelen, notatene, pre-release-flaggetJa
Endre taggnavnet eller mål-commitenNei
Flytte taggen med git push --force, eller slette den med git push --deleteNei
Gjøre utgivelsen om til et utkast igjenNei
Legge til, gi nytt navn til, erstatte eller slette en ressursNei
Slette utgivelsenJa, men taggnavnet kan aldri brukes igjen i det repositoryet

Alle flater håndhever den samme regelen: nettgrensesnittet, REST-API-et (HTTP 422 med en melding som begynner med immutable release:) og git selv (pushen avvises av pre-receive-hooken med en melding som navngir taggen).

Fryingen virker bare fremover. Å slå på innstillingen fryser ingenting som allerede var publisert, og å slå den av senere tiner ingenting. Hvis en frosset utgivelse virkelig må endres, kontakter du support: support kan oppheve fryseren for den ene utgivelsen, med en skriftlig begrunnelse som vises i revisjonsloggen din.

Publiser ressursene før du publiserer utgivelsen

En publisert uforanderlig utgivelse godtar ingen nye ressurser, så rekkefølgen betyr noe. Opprett utgivelsen som et utkast, last opp ressursene og publiser den deretter:

# 1. draft
curl -sS -X POST -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
  -d '{"tag_name":"v1.2.3","name":"v1.2.3","draft":true}' \
  https://frem.sh/api/v1/repos/acme/web/releases
# 2. assets (repeat per file)
curl -sS -X POST -H "Authorization: token $TOKEN" -F attachment=@dist/web-linux-amd64.tar.gz \
  "https://frem.sh/api/v1/repos/acme/web/releases/$RELEASE_ID/assets?name=web-linux-amd64.tar.gz"
# 3. publish — this is the moment it freezes
curl -sS -X PATCH -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
  -d '{"draft":false}' https://frem.sh/api/v1/repos/acme/web/releases/$RELEASE_ID

Utgivelsesverktøy som oppretter en publisert utgivelse og laster opp etterpå, vil feile ved opplastingen med 422. Ressurser som bare er eksterne lenker, kan ikke være del av en uforanderlig utgivelse, fordi det ikke finnes lagrede bytes å ta en digest av; publisering av en slik avvises.

Hver ressurs har nå digesten sin i API-et, slik som hos GitHub: "digest": "sha256:<hex>", og en uforanderlig utgivelse rapporterer "immutable": true.

Container-tagger

Den samme policyen kan fryse tagger for containerimager i registeret:

ModusFrosset
off (standard)ingenting: alle tagger kan overskrives
semverversjonstagger: v1.2.3, 1.2.3, 1.2.3-rc.1; latest, 1, 1.2, main, sha-… kan fortsatt flyttes
allalle tagger unntatt de flyttbare du lister opp (latest, edge-*, …)

En push av en frosset tagg som peker den mot en annen digest, avvises med registerfeilen DENIED (HTTP 403). Å pushe det samme imaget igjen er tillatt, så en CI-jobb som kjøres på nytt, feiler ikke. Sletting av en frosset tagg avvises, og oppryddingsregler for pakker velger aldri en slik; frosne tagger teller fortsatt med i lagringskvoten din. Signatur- og attesteringstaggene som cosign skriver (sha256-<digest>.sig, .att, .sbom), kan alltid flyttes. Container-tagger vurderes mot den gjeldende policyen, så å slå av modusen er måten å flytte eller slette en tagg bevisst på.

Pakker

Publiserte pakkeversjoner er uforanderlige på fremforge: npm, Maven, PyPI, NuGet, Cargo, Go, Helm, Conan, Composer, RubyGems, Pub, Swift, Conda, CRAN, Debian, RPM, Alpine, Arch, Chef, Vagrant og generiske pakker. Dette er slått på for alle organisasjoner som standard. Containere følger i stedet modusen for container-tagger over.

EndringTillatt?
Laste opp en ny versjonJa
Legge til en ny fil i en versjon som finnes (enda en wheel for en plattform, en Maven-classifier-jar)Ja
Laste opp en fil igjen med de samme bytene (en CI-jobb som kjøres på nytt)Ja, endrer ingenting
Erstatte en fil i en publisert versjon med andre bytesNei
Slette en versjon, eller en av filene i denJa
Publisere en slettet versjon igjen, med hvilke som helst bytesNei: versjonsnavnet forblir pensjonert
Laste opp en slettet fil igjen med andre bytesNei

En avvist opplasting svarer med HTTP 409 og en melding som begynner med immutable package:, og svarheaderen X-Fremforge-Immutable sier hvorfor: overwrite, reupload eller retired. Hvert avslag skrives til revisjonsloggen din som package.immutable.refused, med pakken, versjonen, filen, begge SHA-256-digestene og hvem som prøvde. Det vises også i tilgangsloggen for registeret som en blokkert opplasting.

De fleste registre nekter allerede å publisere en versjon to ganger. Dette er det som endres her:

  • Helm og Conan lot tidligere en versjon overskrives på stedet.
  • I alle registre kunne en versjon slettes og deretter publiseres igjen med annet innhold. Begge deler avvises nå.

Alltid foranderlig:

  • Maven--SNAPSHOT-versjoner og maven-metadata.xml-filer, som skrives om etter design.
  • Indeksfilene for repositoryer som Debian-, RPM-, Alpine- og Arch-registre vedlikeholder.

For å holde andre versjoner foranderlige, for eksempel en generisk pakke som brukes som CI-buffer, lister du dem som unntak på formen [<type>:][<name>@]<version>, der * og ? fungerer som jokertegn:

OppføringHolder foranderlig
generic:ci-cache@*alle versjoner av den generiske pakken ci-cache
npm:@acme/ui@0.0.0-dev*utviklingsbygg av én npm-pakke
*-nightly*-nightly-versjonene av alle pakker

En organisasjon kan også slå pakkeuforanderlighet helt av, eller tilbake til plattformens standard (inherit). Innstillingen vurderes når en skriving skjer, og navnene på versjoner som ble slettet mens den var av, huskes fortsatt. Å slå den på beskytter derfor også versjoner som ble slettet tidligere.

Slå det på

Eiere av organisasjonen: Sikkerhet › Policyer › Uforanderlige utgivelser i konsollen (/<org>/_admin/security/policies/immutable-releases), eller API-et med et token som har policy:write (å lese innstillingene krever findings:read):

curl -sS -X PUT -H "Authorization: Bearer $FFP_TOKEN" -H 'Content-Type: application/json' \
  -d '{"releases":true,"allow_repo_opt_out":false,"oci_mode":"semver","oci_mutable_tags":["latest"]}' \
  https://frem.sh/_app/api/v1/orgs/acme/code-security/immutability

Pakkeuforanderlighet bruker det samme endepunktet. packages er inherit (følg plattformens standard, som er på), on eller off, og package_mutable_versions lister unntakene. Svaret rapporterer også packages_effective:

curl -sS -X PUT -H "Authorization: Bearer $FFP_TOKEN" -H 'Content-Type: application/json' \
  -d '{"packages":"inherit","package_mutable_versions":["generic:ci-cache@*"]}' \
  https://frem.sh/_app/api/v1/orgs/acme/code-security/immutability

Et repository kan overstyre organisasjonen: on fryser alltid utgivelser der, selv om organisasjonens standard er av; off velger det bort og respekteres bare når organisasjonen tillater bortvalg; inherit følger organisasjonen.

curl -sS -X PUT -H "Authorization: Bearer $FFP_TOKEN" -H 'Content-Type: application/json' \
  -d '{"releases":"on"}' https://frem.sh/_app/api/v1/orgs/acme/code-security/immutability/repos/web

Pull-speil fryses aldri: taggene deres tilhører repositoryet de speiler.

Utgivelsesattesteringen

For hver uforanderlige utgivelse signerer fremforge én in-toto-erklæring av typen https://in-toto.io/attestation/release/v0.1. Subjektene er:

  • hver ressurs, med navn og SHA-256 (beregnet mens filen ble lastet opp);
  • kilden, git+https://frem.sh/<org>/<repo>@refs/tags/<tag>, med gitCommit.

Erklæringen hentes fra et manifest som Forgejo skriver i den samme transaksjonen som publiserer utgivelsen, og kontrolleres på nytt mot den aktive utgivelsen før den signeres. Den signeres med de samme nøklene som alle andre fremforge-attesteringer (en Ed25519-DSSE-signatur, pluss en nøkkelfri Sigstore-bunt fra fremforges egen Fulcio og tidsstempelmyndighet) og føres inn i transparensloggen. Attesteringen serveres av API-et og CLI-en; den legges aldri ved selve utgivelsen.

Signering forsinker aldri en publisering. En utgivelse publiseres først og signeres kort tid etter, så en kort stund er attesteringen pending. Pending er ikke godkjent: verifiseringen sier PENDING og avslutter med sin egen kode.

Verifisere en utgivelse

fremforge release verify acme/web v1.2.3 ./web-linux-amd64.tar.gz ./checksums.txt

Den henter attesteringen, verifiserer signaturen mot den publiserte tillitsroten (https://www.frem.sh/.well-known/slsa-trust-root.json), sjekker at erklæringen gjelder dette repositoryet og denne taggen, hasher hver fil du oppgir på din maskin og sammenligner med den signerte digesten, og sjekker at konvolutten står i transparensloggen. En fil som utgivelsen ikke oppgir, feiler.

AvslutningskodeBetydning
0PASS
1FAIL (signaturen, erklæringen, digesten til en fil, loggen, eller attesteringen ble avvist)
2bruksfeil
3PENDING: publisert, ikke signert ennå; kjør på nytt om litt

Frakoblet, helt uten nettverk, med konvolutten og en kopi av tillitsroten:

fremforge release verify acme/web v1.2.3 ./web-linux-amd64.tar.gz \
  --offline --envelope=release.intoto.jsonl --trust-root=slsa-trust-root.json

Frakoblet verifisering sjekker signaturen, erklæringen og filene dine; den kan ikke sjekke attesteringens status eller transparensloggen, og sier det.

Rådataene ligger på GET /_app/api/v1/orgs/{org}/repos/{repo}/releases/{tag}/attestation (findings:read): status (pending, signed, failed, unfrozen), subjektene, manifestet og, når den er signert, kortlivede URL-er til DSSE-konvolutten og Sigstore-bunten. Bare signed har en konvolutt.

Ikke dekket

  • De automatisk genererte kildearkivene (.zip og .tar.gz som Forgejo tilbyr for hver tagg) attesteres ikke: bytene deres er ikke stabile på tvers av git-versjoner. Kildesubjektet oppgir i stedet taggens commit; verifiser en utsjekking med git rev-parse v1.2.3^{commit}.
  • Utgivelser som ble publisert før uforanderlighet ble slått på, er ikke frosset og har ingen utgivelsesattestering.

Feil

FlateFormat
Forgejo REST-API422 {"message": "immutable release: …"}
git pushremote: … pre-receive hook declined med begrunnelsen
Containerregister403 {"errors":[{"code":"DENIED", …}]}
fremforge-APInot-enabled, invalid-value, invalid-repo, forgejo-error, not-found