Ein AWS Access Key in einem GitHub Actions Secret ist ein Passwort, das nie abläuft, bis jemand es manuell rotiert. Genau das macht ihn zum bevorzugten Ziel: er liegt fest an einem Ort, gilt oft über Monate, und die Rechte dahinter sind meist großzügiger zugeschnitten, als der einzelne Workflow braucht. Wird er kopiert, aus einem Log ausgelesen oder aus einem kompromittierten Dependency-Paket exfiltriert, funktioniert er so lange weiter, bis jemand den Diebstahl bemerkt und den Key widerruft. Genau dieses Muster, gespeicherte, langlebige Credentials in der Pipeline, ist der wiederkehrende Fund in Berichten zu CI/CD-Kompromittierungen.
OIDC Workload Identity Federation dreht das Modell um. Statt eines gespeicherten Secrets stellt der CI-Anbieter pro Lauf ein signiertes JSON Web Token aus. Die Cloud prüft dieses Token gegen eine Trust Policy und stellt im Gegenzug ein kurzlebiges Zugriffstoken aus, das nur für die Dauer des Jobs gilt. Es gibt kein Secret mehr, das irgendwo liegt und gestohlen werden kann, weil es keins mehr gibt.
Wie der Trust Handshake abläuft
GitHub Actions tritt dabei als OpenID Connect Identity Provider auf. Für jeden Job, der id-token: write in seinen Permissions gesetzt hat, kann der Workflow ein JWT anfordern. Dieses Token enthält unter anderem folgende Claims:
iss: https://token.actions.githubusercontent.com
sub: repo:meine-org/mein-repo:ref:refs/heads/main
aud: sts.amazonaws.com
repository: meine-org/mein-repo
workflow: deploy.yml
Die Cloud Seite, egal ob AWS, GCP oder Azure, kennt den öffentlichen Schlüssel von token.actions.githubusercontent.com und kann die Signatur des Tokens prüfen, ohne dass GitHub und die Cloud vorher ein gemeinsames Secret ausgetauscht haben. Danach entscheidet eine Trust Policy anhand der Claims im Token, ob und mit welcher Rolle Zugriff gewährt wird. Zwei Claims tragen hier die eigentliche Sicherheitsentscheidung: sub bindet den Zugriff an ein konkretes Repository, einen Branch oder ein Environment, aud stellt sicher, dass das Token wirklich für diese eine Cloud ausgestellt wurde und nicht für einen anderen Dienst umgewidmet werden kann. Das ausgestellte Zugriffstoken selbst ist danach nur für die Laufzeit des Jobs gültig, nicht für den nächsten Lauf und nicht wiederverwendbar, wenn der Job längst durch ist.
AWS in der Praxis: Trust Policy und Workflow
Auf der AWS Seite richtest du einmalig einen OIDC Identity Provider für token.actions.githubusercontent.com ein und hängst eine IAM Rolle mit passender Trust Policy daran:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:meine-org/mein-repo:ref:refs/heads/main"
}
}
}
]
}
Im Workflow selbst brauchst du dafür kein einziges gespeichertes Secret mehr:
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gh-actions-deploy
aws-region: eu-central-1
- run: aws s3 ls
Die Action fordert das JWT selbst an, tauscht es gegen ein STS Token, und exportiert die resultierenden temporären Zugangsdaten als Umgebungsvariablen für die folgenden Steps. Nach Ende des Jobs sind sie wertlos.
GCP und Azure kurz skizziert
Bei Google Cloud heißt der äquivalente Mechanismus Workload Identity Federation: du legst einen Workload Identity Pool und darin einen Provider an, der GitHub als Identity Provider akzeptiert, mit einer Attribute Condition, die wieder auf assertion.repository prüft. Bei Azure trägst du an einer App Registrierung eine Federated Credential ein, mit token.actions.githubusercontent.com als Issuer und demselben sub Muster wie bei AWS. In beiden Fällen ist die Grundidee identisch zu AWS: kein gespeichertes Secret, sondern eine Trust Beziehung, die pro Lauf neu geprüft wird.
Typische Fallstricke bei der Migration
Der häufigste Fehler ist ein zu weiter sub Wildcard. Eine Bedingung wie repo:meine-org/*:* erlaubt jedem Repository und jedem Branch der Organisation, dieselbe Rolle zu übernehmen, das ist im Ergebnis kaum enger als ein geteilter Access Key. Die Bedingung gehört auf ein konkretes Repository und, wo möglich, auf ein Environment oder einen Branch eingeschränkt, nicht auf die ganze Organisation.
Der zweite Fehler ist eine Trust Policy ohne aud Prüfung. Fehlt die Bedingung auf token.actions.githubusercontent.com:aud, verlässt sich die Rolle allein auf sub, und ein Token, das eigentlich für einen anderen Zweck ausgestellt wurde, aber zufällig einen passenden sub Claim trägt, würde ebenfalls akzeptiert. Beide Bedingungen gehören zusammen in die Policy, nicht nur eine davon.
Der dritte Punkt betrifft die Migration selbst. Alte Pipelines laufen häufig noch eine Weile mit dem gespeicherten Access Key parallel zur neuen OIDC Rolle, während die Umstellung getestet wird. Genau in dieser Übergangsphase bleibt der alte Key aktiv und wird oft schlicht vergessen, sobald die neue Rolle einmal läuft. Ohne einen festen Termin, an dem der alte IAM User deaktiviert und der Key gelöscht wird, bleibt die eigentliche Angriffsfläche, die die Migration beseitigen sollte, einfach liegen.
Wie du Rechte in der Pipeline generell auf das Nötige zuschneidest, unabhängig vom Auth Mechanismus, hast du im Post vom 10.06. zu GitHub Actions Pinning und Least Privilege gesehen. OIDC Workload Identity ersetzt darin genau einen Baustein, das gespeicherte Cloud Secret, nicht die Sorgfalt bei der Rechtevergabe selbst.
Hinterlasse einen Kommentar