Die meisten CI/CD-Pipelines laufen mit viel mehr Rechten als sie brauchen. Ein Workflow, der eigentlich nur Tests ausführen soll, hat Schreibzugriff auf das Repository, kann Releases anlegen und liest nebenbei alle Secrets aus. Das fällt niemandem auf, solange nichts passiert. Wenn eine Dependency kompromittiert wird oder ein Pull Request von außen Code einschleust, entscheidet genau dieser Rechteumfang darüber, wie groß der Schaden wird.
Warum großzügige Rechte der Normalfall sind
Rechte werden vergeben, wenn etwas nicht funktioniert. Der Build scheitert an einer fehlenden Berechtigung, jemand setzt sie großzügig, und danach fasst sie keiner mehr an. Bei GitHub Actions kommt dazu, dass der Standard lange auf der großzügigen Seite lag: Ohne explizite Angabe bekam der GITHUB_TOKEN in vielen Repositories Schreibrechte auf so gut wie alles. Wer nie eine permissions-Angabe schreibt, erbt genau dieses Verhalten.
Least Privilege heißt, den umgekehrten Weg zu gehen: mit null Rechten starten und nur das freigeben, was der konkrete Job tatsächlich braucht. Das ist etwas mehr Arbeit beim Aufsetzen und deutlich weniger Angriffsfläche danach.
Der Standard auf lesend setzen
Der erste Schritt betrifft nicht einen einzelnen Workflow, sondern das ganze Repository. In den Repository-Einstellungen unter Actions lässt sich der Default-Token auf Read repository contents permission stellen. Ab diesem Punkt startet jeder Workflow ohne Schreibrechte, und jeder Job, der mehr braucht, muss es explizit anfordern. Das macht Rechte sichtbar, statt sie im Hintergrund zu verteilen.
Im Workflow selbst gehört dann ganz oben ein minimaler Block, der als Basis für alle Jobs dient:
permissions: contents: readjobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm test
Dieser Job liest den Code aus und führt Tests aus. Mehr braucht er nicht, also bekommt er auch nicht mehr. Kein Schreibzugriff auf Contents, keine Rechte an Packages, Deployments oder Issues.
Rechte pro Job vergeben, nicht pro Workflow
Interessant wird es, sobald ein Workflow mehrere Jobs hat. Rechte lassen sich pro Job überschreiben, und genau das sollte man nutzen. Der Test-Job bleibt lesend, der Release-Job bekommt exakt die Schreibrechte, die er braucht, und nichts darüber hinaus.
permissions: contents: readjobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test release: needs: test runs-on: ubuntu-latest permissions: contents: write id-token: write steps: - uses: actions/checkout@v4 - run: npx semantic-release
Der contents: write-Scope existiert nur im Release-Job und nur für die Dauer dieses Jobs. Wird der Test-Job durch eine manipulierte Dependency kompromittiert, kann der Angreifer trotzdem nichts ins Repository schreiben, weil dieser Job diese Rechte schlicht nicht hat.
Keine langlebigen Cloud-Credentials im Klartext
Least Privilege endet nicht am Repository. Wer aus der Pipeline in AWS, GCP oder Azure deployed, sollte keine statischen Access Keys als Secret hinterlegen. Ein einmal geleakter Key gilt so lange, bis ihn jemand aktiv rotiert. Über id-token: write und OIDC tauscht der Workflow stattdessen ein kurzlebiges Token gegen eine Rolle beim Cloud-Provider. Das Token lebt nur für den Lauf und ist an genau dieses Repository gebunden.
deploy:
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/deploy
aws-region: eu-central-1
Auf der IAM-Seite bekommt diese Rolle wiederum nur die Aktionen, die das Deployment braucht. Least Privilege in der Pipeline und Least Privilege in der Cloud greifen hier ineinander. Beides großzügig zu lassen, hebt den Nutzen des jeweils anderen wieder auf.
Woran man merkt, dass es zu viel war
Ein guter Test ist, die Rechte bewusst zu klein zu setzen und zu schauen, wo der Workflow scheitert. Die Fehlermeldung zeigt genau, welcher Scope gefehlt hat, und den fügt man dann gezielt hinzu. Das ist mühsamer als einmal write-all zu setzen, aber am Ende steht eine Pipeline, deren Rechte man erklären kann. Genau das ist der Punkt: Jedes Recht sollte eine Begründung haben, die über „sonst ging es nicht“ hinausgeht.
Wer das Thema systematisch angehen will, findet auf digital-business.blog weitere Beiträge dazu, wie sich solche Prinzipien über die ganze Lieferkette hinweg verankern lassen, statt sie in einzelnen Workflows zu reparieren.
Hinterlasse einen Kommentar