· · 18 min leestijd Advanced

Supply chain security in GitLab CI: SBOM, cosign en het beleid dat blokkeert

Genereer een SBOM in GitLab CI, onderteken de image-digest met cosign, hang de SBOM eraan als attestation, en laat Kyverno alles weigeren wat niet ondertekend is. Plus een eerlijke lezing van welke SLSA level 3-eisen een GitLab-runner haalt.

Een verzegelde krat met paklijst passeert een uitsmijterrobot, een onverzegelde met een eerlijk gezicht niet

TL;DR Een pipeline die images scant en een SBOM schrijft, zonder dat iets daar bij toelating naar kijkt, stond een jaar op groen en hield niks tegen. Vier stappen hieronder: een SBOM die Trivy kan teruglezen, een cosign-handtekening op de digest met de sleutel in Vault, de SBOM eraan gehangen als attestation, en een Kyverno-policy die de rest weigert. Daarna een tabel van welke SLSA Build L3-eisen een zelfgehoste GitLab-runner haalt, en de twee die hij niet haalt.

Ik nam bij een klant een pipeline over, op hun eigen GitLab, self-managed op eigen hardware. Op papier deed hij alles goed.

Trivy scande elke image. Een tweede job schreef een SBOM , toegevoegd in het kwartaal waarin de securityvragenlijst van een afnemer vroeg of ze die maakten. Beide jobs stonden op groen, en dat al ongeveer een jaar. Beide zetten een JSON-bestand in de artifact-opslag van GitLab, waar het dertig dagen stond en dan verliep. Iemand die er ooit eentje had geopend heb ik niet gevonden.

Ondertussen startte het cluster gewoon alles. Elke image uit elke registry, zolang degene die pushte maar een token had. De scan rapporteerde over iets dat al live stond, en de SBOM was een bestand zonder lezer.

Niemand had iets fout gedaan, en dat wil ik gezegd hebben voor de rest van dit stuk. Elke job in dat bestand was toegevoegd door iemand die het probleem uit z’n eigen ticket oploste, en elke job werkte. De scanjob vond kwetsbaarheden. De SBOM-job maakte een SBOM. Wat ontbrak was de stap die van een van beide een beslissing maakt, en die stap stond in niemands ticket, dus schreef niemand hem. Tegen de tijd dat ik dat bestand las waren beide mensen wier naam onder die commits stond al weg.

Daar krijg ik jeuk van, en ja, dat is beroepsdeformatie. Ik doe bij klanten geregeld schaduw-CISO -werk, en de gaten die ik daar vind zitten zelden in een job die iemand heeft geschreven. Ze zitten tussen twee tickets in, waar geen eigenaar is en beide kanten aannemen dat de ander het dekt. Een auditor ziet twee groene jobs en zet een vinkje. Het cluster ziet geen van beide jobs ooit.

Het gat

Dit is wat dat bij elkaar opgeteld is. De supply chain stond in een document beschreven, en niks liet het cluster ernaar handelen.

Drie gaten, en volgens mij is het er één:

  1. De SBOM had geen afnemer. Hij beantwoordt “welke van m’n images bevatten deze bibliotheek” alleen als iets hem later terugvindt, en een job-artifact dat na dertig dagen verloopt is niet terug te vinden.
  2. Niks verbond de image aan de pipeline. De registry had een tag. Iedereen met pushrechten kan een tag verplaatsen. Niks in de image zei “dit komt uit die pipeline, van die commit”.
  3. Het cluster verifieerde niks. Kyverno draaide met een stapel governance-policies over resource limits en namespaces, en geen ervan gaf iets om waar een image vandaan kwam.

Er is ook een compliance-versie hiervan, en die is erger omdat hij prima klinkt. Op een pagina in de interne wiki stond dat het platform SLSA level 3 nastreeft. Er stond een datum onder en geen auteur. Niemand die er nog werkte kon me vertellen welke eisen uit die specificatie een GitLab-runner nou echt haalt, en ik ook niet totdat ik ervoor ging zitten. Daar kom ik verderop op terug. De korte versie is dat hij ze niet allemaal haalt, en eentje structureel niet kán.

Dus wat houdt een niet-ondertekende image nou echt tegen?

Vier bewegende delen. De eerste drie zijn voorbereiding, het vierde is een poort die nee zegt. Wat volgt gaat ervan uit dat je al een GitLab-pipeline hebt die een image bouwt en uitrolt; elke job hieronder past daarin.

Stap een: een SBOM die het afnemen waard is

Begin bij Trivy, die waarschijnlijk toch al in je pipeline zit. Het commando is kort en er zit één valkuil in:

trivy image --format cyclonedx --output sbom.cdx.json "$IMAGE"

Draai dat en Trivy drukt een regel af waar de meeste mensen langs scrollen:

"--format cyclonedx" disables security scanning. Specify "--scanners vuln"
explicitly if you want to include vulnerabilities in the CycloneDX report.

De standaard geeft je dus een inventaris zonder bevindingen. Ik vind dat eigenlijk de juiste standaard. Een SBOM is een onderdelenlijst, een scan is een oordeel over die lijst op een gegeven dag, en die twee verouderen niet even snel. Het is alleen niet wat de meeste mensen denken te krijgen. Wil je allebei in één document:

trivy image --scanners vuln --format cyclonedx --output sbom.cdx.json "$IMAGE"

Ik maak de inventaris zonder bevindingen en scan apart. De onderdelenlijst is een feit over de image en blijft waar zolang de image bestaat. De kwetsbaarhedenlijst klopt op dinsdag en op donderdag niet meer, dus die hoort in een geplande job die herscant wat al draait, niet vastgelast in het artefact.

De andere helft van “het afnemen waard” is dat Trivy een SBOM terugleest als scandoel:

trivy sbom sbom.cdx.json

Dát is de lus die het bestand de moeite waard maakt. Over zes maanden, als er een CVE valt in een bibliotheek waar je nog nooit van hebt gehoord, scan je de SBOM’s van alles wat nu draait in plaats van elke image opnieuw te bouwen en te scannen.

Stap twee: onderteken de digest, niet de tag

cosign hangt een handtekening aan een image in de registry. De subtiliteit zit in waar je hem aan hangt.

IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE_URI")
cosign sign "$IMAGE_DIGEST"

myapp:1.4.2 ondertekenen is een naam ondertekenen. Namen verschuiven, ook als het versienummer uit je commits rolt en niemand het met de hand kiest, want een tag blijft een verwijzing en een verwijzing blijft schrijfbaar. myapp@sha256:9f86d0... ondertekenen is één specifieke berg bytes ondertekenen, en dat is de enige versie hiervan die iets betekent. GitLabs eigen tutorial haalt de digest precies daarom uit docker inspect, en hun documentatie geeft nog een regel die ik zou volgen: bouw en onderteken in dezelfde job, zodat niks ertussen een venster krijgt om aan het artefact te zitten.

Waar de sleutel staat, en waarom daar

Dit is de beslissing waar deze post voor bestaat, dus ik zet alle drie de opties neer en zeg waar wij op uitkwamen.

Optie een: een private key in een gemaskeerde CI/CD-variabele. Werkt, kost tien minuten, en bijna iedereen begint hier. Daar word ik alleen niet vrolijk van, want die sleutel is nu een omgevingsvariabele in een build-job, en elk script in die pipeline kan hem lezen, de install-hook van een dependency incluis. Roteren betekent de geschiedenis opnieuw ondertekenen of een breuk in de keten accepteren.

Optie twee: de sleutel blijft in Vault en komt er nooit uit. cosign spreekt KMS, en de transit-engine van HashiCorp Vault is een van de backends:

export VAULT_ADDR=https://vault.example.com
vault secrets enable transit
cosign generate-key-pair --kms hashivault://cosign
cosign sign --key hashivault://cosign "$IMAGE_DIGEST"

De runner heeft de private key nooit in handen. Hij heeft een Vault-token dat om een handtekening mag vragen, en dat is een stuk minder waard om te stelen. Het beleid dat Sigstore hiervoor documenteert geeft read op transit/keys/cosign, update op transit/hmac/cosign/* en transit/sign/cosign/*, en create op transit/verify/cosign, en verder niks. openbao:// is het zusterschema en gedraagt zich hetzelfde, dus deze optie overleeft de overstap weg van Vault als je die ooit maakt. Die van mij staat op de lijst. Draai je al Vault of OpenBao voor je clustersecrets, dan is dit een configuratiewijziging en geen nieuw component.

Optie drie: keyless, met Sigstore. Helemaal geen langlevende sleutel. GitLab geeft cosign een OIDC-token, Fulcio geeft een certificaat uit dat tien minuten leeft, cosign ondertekent ermee en gooit de sleutel weg, en de hele gebeurtenis komt in Rekor te staan. De identiteit in dat certificaat is je project en je CI-configuratiebestand, en dat is een veel betere claim dan “iemand die de sleutel had”.

Op GitLab.com is het hele verhaal het id_tokens-blok, en dat gaat in de job die toch al bouwt:

build:
  stage: build
  image: docker:cli
  services:
    - docker:dind
  variables:
    COSIGN_YES: "true"
    IMAGE_URI: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: sigstore
  before_script:
    - apk add --no-cache cosign
    - docker login -u gitlab-ci-token -p "$CI_JOB_TOKEN" "$CI_REGISTRY"
  script:
    - docker build -t "$IMAGE_URI" .
    - docker push "$IMAGE_URI"
    - IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE_URI")
    - cosign sign "$IMAGE_DIGEST"

Eén job, want IMAGE_DIGEST is een shellvariabele en een tweede job krijgt een andere shell. Trek je het ondertekenen eruit, dan geef je de digest door via een dotenv-report, en staat de image onondertekend in de registry zolang de pipeline erover doet om bij de volgende stage te komen.

cosign pakt SIGSTORE_ID_TOKEN zelf uit de omgeving. COSIGN_YES vervangt de interactieve bevestiging, want een pipeline heeft niemand om iets te bevestigen.

Nu het deel dat de tutorials overslaan. Op een zelfgehoste GitLab kost keyless je drie services. De publieke Sigstore-omgeving vertrouwt alleen pipelines van GitLab.com, dus GitLabs eigen documentatie voor self-managed instances zegt dat je je eigen Fulcio, je eigen Rekor en je eigen Certificate Transparency-log moet draaien. Dat CT-log is niet optioneel, want een Fulcio zonder log geeft certificaten uit die cosign weigert te verifiëren. Rekor heeft een persistente signing key nodig, want een in-memory signer maakt bij een herstart een nieuwe en maakt alles ongeldig wat ervoor is gelogd.

We hebben dit op de self-managed instance van de klant doorgerekend en toch de sleutel in Vault genomen. Drie extra stateful services, waarvan één een append-only log waar de sleutel kwijtraken onherstelbaar is, is een echte rekening, ook voor een team met beheerbudget, en wat je ervoor koopt is een beter identiteitsverhaal bovenop ondertekenen dat al werkt. Soevereiniteit betekent die afweging maken met de kosten in zicht, en hier was de eenvoudige optie de eerlijke. Zit je op GitLab.com, neem dan gewoon de keyless route. Je krijgt de strikt betere identiteit voor vier regels YAML.

Twee valkuilen van die pagina, mocht je de stack toch bouwen. Container images ondertekenen vereist cosign v3 en een registry op een oplosbare HTTPS-hostnaam, dus een instance waarvan de registry_external_url een kaal IP-adres is kan wél bestanden ondertekenen en geen images. En de /healthz van Fulcio meldt gezond, ook als de OIDC-provider niet is opgestart. Daar ben je een middagje zoet mee.

Verifiëren, en de dubbele slash

Welke optie je ook kiest, in het verify-commando komt de identiteit tevoorschijn:

cosign verify "$IMAGE_DIGEST" \
  --certificate-identity "https://gitlab.com/my-group/my-project//.gitlab-ci.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://gitlab.com"

Die dubbele slash tussen het projectpad en het pad van het CI-configuratiebestand is opzettelijk en verplicht. Schrijf één slash en je krijgt:

Error: none of the expected identities matched what was in the certificate, got subjects

wat je niks vertelt over de werkelijke fout. GitLab documenteert het. Ik ben er alsnog twintig minuten in blijven hangen.

Nog een beperking die je wilt kennen voordat je erop bouwt. Het id_tokens-blok moet in het project staan dat gebouwd wordt. Auto DevOps, CI-bestanden die uit een andere repository komen en child pipelines werken niet, wat GitLab bijhoudt als Support keyless signing when CI config is located outside of the project. Centraliseert jouw organisatie pipeline-templates, dan is dat een ontwerpbeperking en wil je hem kennen voordat die templates er zijn.

Stap drie: hang de SBOM aan de image

Ondertekenen bewijst dat een image uit jouw pipeline komt. Attesteren hangt er een ondertekende verklaring over die image aan, en de SBOM is precies zo’n verklaring. Trivy documenteert de combinatie:

trivy image --format cyclonedx -o sbom.cdx.json "$IMAGE_DIGEST"
cosign attest --key hashivault://cosign --type cyclonedx --predicate sbom.cdx.json "$IMAGE_DIGEST"

De SBOM reist nu met de image mee, in de registry, ondertekend met dezelfde sleutel. Trek de image waar dan ook binnen en de onderdelenlijst komt mee. Vergelijk dat eens met een JSON-bestand in artifact-opslag met dertig dagen te leven.

Teruglezen doe je zo:

cosign verify-attestation --key cosign.pub --type cyclonedx "$IMAGE_DIGEST" > sbom.intoto.jsonl
trivy sbom ./sbom.intoto.jsonl

Twee praktische noten. cosign attest schrijft naar de OCI-registry, dus de job heeft pushrechten op die repository nodig, en --no-upload bestaat als je de verklaring alleen wilt bekijken. En alleen attestations van het type CycloneDX kun je terugvoeren aan trivy sbom, dus wilde je om een andere reden SPDX kiezen, kies het dan met dat in gedachten.

Stap vier: laat het cluster weigeren

Alles tot nu toe maakt bewijs. Dit is het deel dat ernaar handelt.

Eerst een noot over welke Kyverno-API je moet gebruiken, want dit is recent veranderd en het bepaalt wat je typt. Het ClusterPolicy-kind met z’n verifyImages-regel is deprecated sinds Kyverno v1.19 en staat gepland om in v1.20 te verdwijnen. De vervanger is ImageValidatingPolicy, die in dezelfde release stable werd en z’n controles in CEL uitdrukt. Schrijf je vandaag een nieuwe policy, schrijf dan deze.

Begin in auditmodus. Ik meen het, elke keer.

apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
  name: require-signed-images
spec:
  validationActions: [Audit]
  failurePolicy: Fail
  webhookConfiguration:
    timeoutSeconds: 15
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
  matchImageReferences:
    - glob: "registry.example.com/platform/*"
  attestors:
    - name: pipeline
      cosign:
        key:
          data: |
            -----BEGIN PUBLIC KEY-----
            MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
            -----END PUBLIC KEY-----
  validations:
    - expression: >-
        images.containers.map(image,
          verifyImageSignatures(image, [attestors.pipeline])).all(e, e > 0)
      message: "Image is not signed by the platform pipeline"

De publieke sleutel komt zo uit Vault, en hij is publiek, dus hem in een policy plakken is prima:

cosign public-key --key hashivault://cosign

Let op dat matchImageReferences een glob op je eigen registry is en geen *. Baken het bewust af. Alles wat je niet zelf hebt gebouwd gaat deze controle niet halen, te beginnen bij de ingress controller.

Laat hem op Audit staan en ga een week lang de policy reports lezen. Je vindt images waarvan je vergeten was dat je ze draaide. Die week is precies waar auditmodus voor bestaat, en hem overslaan is hoe een policy-uitrol een storing wordt.

Verifieer daarna ook de SBOM, want dat is de stap die de lus sluit waar ik bovenaan mee begon. Voeg een attestations:-blok toe, en laat de validations:-lijst die je al hebt groeien van één naar drie. Er is precies één validations:-sleutel in een policy, dus deze vervangen hem in plaats van ernaast te staan:

  attestations:
    - name: sbom
      intoto:
        type: https://cyclonedx.org/bom
  validations:
    - expression: >-
        images.containers.map(image,
          verifyImageSignatures(image, [attestors.pipeline])).all(e, e > 0)
      message: "Image is not signed by the platform pipeline"
    - expression: >-
        images.containers.map(image,
          verifyAttestationSignatures(image, attestations.sbom, [attestors.pipeline])).all(e, e > 0)
      message: "Image has no signed SBOM attestation"
    - expression: >-
        images.containers.map(image,
          extractPayload(image, attestations.sbom).predicate.bomFormat == 'CycloneDX').all(e, e)
      message: "SBOM attestation is not CycloneDX"

https://cyclonedx.org/bom is het predicate type dat cosign attest --type cyclonedx wegschrijft, en met intoto: benoem je een predicate type. Kyverno heeft ook een referrer:-vorm die op een OCI 1.1-artifacttype matcht, en die is hier de verkeerde, want cosign attest schrijft standaard een tag-gebaseerde attestation op sha256-<digest>.att en geen referrer. Kies je referrer:, dan meldt de policy dat de attestation ontbreekt terwijl stap drie succes meldt, en zit je een uur aan de verkeerde kant te zoeken.

De .predicate in de derde expressie staat er om dezelfde reden. cosign verpakt je BOM in een in-toto statement, dus bomFormat zit een niveau lager en niet bovenin.

extractPayload() geeft een fout tenzij verifyAttestationSignatures() eerst over dezelfde attestation is gegaan, en dat is toch al de juiste volgorde. Een payload lezen die je niet hebt geverifieerd betekent de onderdelenlijst van een aanvaller vertrouwen.

Draai je toch een zelfgehoste Sigstore, dan neemt de keyless attestor een trustedRoot.expression die jouw eigen trust root JSON uit een ConfigMap leest, zodat de policy de publieke Sigstore-instantie niet hoeft te vertrouwen.

Als de rapporten stil zijn, verander je één woord:

  validationActions: [Deny]

Bewijs dat hij blokkeert

Geloof de policy niet op z’n woord. Push een niet-ondertekende image en kijk hoe de API-server hem weigert:

docker pull alpine:3.22
docker tag alpine:3.22 registry.example.com/platform/unsigned-test:1
docker push registry.example.com/platform/unsigned-test:1

kubectl run blocked --image=registry.example.com/platform/unsigned-test:1
Error from server: admission webhook "ivpol.validate.kyverno.svc-fail" denied the request:
ImageValidatingPolicy require-signed-images: Image is not signed by the platform pipeline

Draai daarna dezelfde test met een image die je pipeline heeft gebouwd en ondertekend, en kijk hoe hij start. Beide helften tellen. Een policy die alles blokkeert is ook kapot, en dat falen ziet er op een compliance-rapport uit als succes.

Zet beide commando’s in een geplande pipeline. Policies verlopen, webhooks gaan tijdens een incident uit en niemand zet ze terug aan, en een gecachet resultaat kan een probleem een uur verbergen. Wat me bij een cachedetail brengt dat je wilt weten. Kyverno cachet de resultaten van image-verificatie, met imageVerifyCacheTTLDuration standaard op 60m. Een handtekening die je in de registry intrekt wordt geaccepteerd tot die cache-regel verloopt.

SLSA level 3, en wat een GitLab-runner werkelijk haalt

Nu de compliance-claim van bovenaan, serieus genomen.

Eerst een bronvermeldingscorrectie die veel schrijfsels over dit onderwerp raakt, inclusief dingen die ik zelf had bewaard. SLSA v1.0 is retired. De huidige specificatie is v1.2, en die heeft de URL’s herschikt, dus de niveaubeschrijvingen staan nu op build track basics en de details op build requirements. De tekst van de Build-track zelf is nauwelijks veranderd; wat v1.2 toevoegt is een Source-track. Citeer je spec/v1.0, dan citeer je een ingetrokken document.

De Build-track gaat zo. L1 is provenance die bestaat. L2 is ondertekende provenance van een gehost buildplatform, gericht tegen “tampering after the build”. L3 is een gehard buildplatform, gericht tegen “tampering during the build”, en voegt twee eisen toe: Isolated en Provenance is Unforgeable.

GitLabs eigen claim is precies en makkelijk te missen. De documentatie zegt dat “GitLab offers a SLSA level 2 compliant provenance statement”, gemaakt door de runner zelf zodra je RUNNER_GENERATE_ARTIFACTS_METADATA: "true" zet. Niveau twee. Er is een aparte SLSA level 3-feature, en de kop lezen is de snelste manier om te weten of die voor jou geldt. Tier Ultimate, Offering GitLab.com, Status Experiment, achter een feature flag genaamd slsa_provenance_statement die standaard uit staat, en het project moet publiek zijn. Een self-managed instance kan hem helemaal niet gebruiken.

Waar laat dat een zelfgehoste runner? GitLab publiceert geen gap-analyse, dus wat nu volgt is mijn lezing van de spectekst naast hoe een runner werkt, en niemands officiële standpunt.

Eis van SLSA v1.2 Build L3Een GitLab-runner, docker executor
Hosted, niet het werkstation van een individuGehaald
Ephemeral omgeving per buildGehaald. Verse container per job
Overlappende builds kunnen elkaar niet beïnvloedenGehaald, voor zover containerisolatie standhoudt
Buildresultaat identiek met en zonder cacheNiet bewezen. cache: wordt per key over jobs gedeeld
Signing-materiaal onbereikbaar voor de buildstappenNiet gehaald
Elk provenance-veld gegenereerd in een trusted control planeNiet gehaald

De laatste twee zijn de belangrijke, en ze zijn structureel, dus geen instelling die je vergeten bent lost ze op. De spec zegt dat signing-materiaal “MUST NOT be accessible to the environment running the user-defined build steps”. Of je de job nu een cosign-sleutel uit een CI-variabele geeft of een SIGSTORE_ID_TOKEN uit id_tokens, de credential komt binnen als omgevingsvariabele in dezelfde shell die je buildscript draait. En de provenance die de runner maakt ontstaat op dezelfde machine die de job draait, dus wie daar willekeurige commando’s kan uitvoeren zit in een positie om hem te beïnvloeden. Het woord van de spec voor de eigenschap die je nodig hebt is “strongly resistant to forgery by tenants”, en een control plane die je bedient als dezelfde persoon die de pipeline schrijft is dat niet.

De Vault-optie maakt dit kleiner. De private key bereikt de runner echt nooit. De job kan Vault nog steeds vragen iets van eigen keuze te ondertekenen, dus het verplaatst het probleem van sleuteldiefstal naar misbruik van een signing oracle. Kleiner probleem. Nog steeds een probleem.

Eén zin uit de spec houdt de hele exercitie eerlijk: “Build L3 is limited to ensuring that a well-intentioned build runs securely.” Het weerhoudt je er niet van een riskante build te schrijven. Niks hier doet dat.

Mijn conclusie, en ik draai er niet omheen. Een zelfgehoste GitLab haalt Build L2 en de meeste isolatie-eigenschappen van L3, en haalt L3 niet. Ik zet geen niveaunummer meer in documenten. Wat ik in plaats daarvan documenteer is het verificatiecommando, want dat is een claim die een lezer kan natrekken.

De runner zelf harden is een echt en groot onderwerp, en dat krijgt een eigen post.

Wat dit je niet oplevert

Even bot, want het plaatje doet het groter lijken dan het is.

Een handtekening bewijst dat een image uit jouw pipeline komt. Over de kwaliteit van de code erin zegt hij niks. Onderteken een achterdeur en je krijgt een correct ondertekende achterdeur, en de policy laat hem vrolijk door.

Verificatie is ook een dependency. cosign v3.1.3, uitgebracht op 6 augustus 2026, repareert GHSA-fx35-mq7g-6g98, gewaardeerd op 7,4. Als het certificaat in een legacy JSON-bundle niet als X.509 te parsen was, viel cosign terug op het lezen ervan als kale publieke sleutel, waardoor de ketenvalidatie werd overgeslagen en --certificate-identity en --certificate-oidc-issuer niks meer deden. Image-verificatie is niet geraakt, dus geen van de commando’s in deze post ook, maar verify-blob wel. De vorm van de bug is wat ik zou meenemen. Wat jouw handtekeningen controleert is software met bugs erin.

En de versies in deze post bewegen snel. Op de dag dat ik hem schreef: cosign v3.1.3, Kyverno v1.19.1, Trivy v0.74.0. Kyverno’s verwijdering van ClusterPolicy in v1.20 is een aangekondigd plan, en geen release heeft het nog uitgevoerd. Controleer je eigen versies in plaats van de mijne te geloven.

Waar het om ging

Drie van de vier stappen in deze post maken bewijs. Eén handelt ernaar, en die pipeline had ongeveer een jaar lang de drie en niet de ene. Het meeste supply chain-werk dat ik zie heeft die vorm. Enorm veel moeite gaat naar artefacten maken, en bijna niks naar het ding dat weigert.

Wat het me heeft opgeleverd heeft niks te maken met het niveaunummer of de vragenlijst. Ik kan “wat draait er in dit cluster en waar komt het vandaan” nu met een commando beantwoorden, waar het eerst een overtuiging was. Alles op deze site komt terug op die voorkeur. Ik begrijp liever wat ik draai dan dat ik het vertrouw.

Begin met Audit en één glob, lees een week lang de rapporten, en zet hem op Deny als ze stil worden. Blijf daarna de niet-ondertekende testimage op een schema pushen.