AWS_ACCESS_KEY_ID in CI-Variablen war 2018 Standard. 2026 ist es ein Antimuster. Föderierte Identität über OIDC liefert der Pipeline kurzlebige STS-Credentials, eingegrenzt durch den sub-Claim des Tokens, und CloudTrail protokolliert nicht mehr einen namenlosen Machine-User, sondern den konkreten Workflow mit Branch und Repository. Dasselbe Muster trägt für GitHub Actions, GitLab CI und ein selbst gehostetes Atlantis.
Was am langlebigen Schlüssel falsch ist
Ein aws_iam_access_key lebt Monate. Er steht in den secrets des Repositories oder in CI-Variablen, und das erzeugt drei Probleme, die keine „bessere Passwortrichtlinie“ heilt:
- Manuelle Rotation. Den Schlüssel neu auszustellen heißt: ein PR in der SaaS-Plattform, ein neuer Wert in jedem Secret jedes Repositories und die Hoffnung, dass niemand ihn in
~/.aws/credentialsauf seinem Laptop liegen hat. - Blast Radius bei Leck. Ein Schlüssel mit Recht auf
terraform applyfunktioniert ebenso aus der Shell eines Angreifers wie aus Ihrem CI. Die Netzwerkgrenze ist0.0.0.0/0. - CloudTrail ohne Attribution. Sie sehen
eventSource=ec2.amazonaws.com,userIdentity.userName=ci-deployer-prod, aber keinerlei Bezug dazu, welcher PR oder wessen Klick es ausgelöst hat.
OIDC als Ersatz: drei Akteure
Der föderierte Ablauf ersetzt „Schlüssel weitergeben“ durch „Token gegen kurze Session tauschen“. Er hat drei Akteure:
- Der CI-Anbieter stellt ein signiertes JWT aus (das
id_token) mit Claims zu Repository, Branch, Environment und Projekt-ID. - Ein IAM OIDC Identity Provider in AWS — einer pro Anbieter, ARN in der Form
arn:aws:iam::<acc>:oidc-provider/<issuer-host>. Audience ist immersts.amazonaws.com. - Eine IAM-Rolle mit Trust Policy — der
Federated-Principal zeigt auf den OIDC-Provider, dieConditionfiltert Anfragen über densub-Claim.
Wenn ein Job startet, legt das CI das JWT in eine temporäre Variable und ruft sts:AssumeRoleWithWebIdentity auf. AWS prüft die Signatur, vergleicht aud und sub mit der Trust Policy und liefert AccessKeyId/SecretAccessKey/SessionToken, gültig für eine Stunde oder weniger. CloudTrail zeigt nicht mehr iam-user/ci-prod, sondern arn:aws:sts::…:assumed-role/<role>/<run-id> mit dem sub-Claim in webIdFederationData.
Minimales Setup auf drei Anbietern
GitHub Actions — Issuer token.actions.githubusercontent.com, Sub-Format repo:org/repo:ref:refs/heads/main (oder :environment:prod, oder :pull_request):
permissions:
id-token: write # erforderlich
contents: read
jobs:
deploy:
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::<acct>:role/gha-deploy
aws-region: eu-central-1
- run: terraform apply -auto-approve
GitLab CI (SaaS) — Issuer https://gitlab.com, Sub-Format project_path:group/project:ref_type:branch:ref:main. Das JWT kommt als id_tokens an, der Tausch ist ein expliziter aws sts assume-role-with-web-identity-Schritt.
Selbst gehostetes Atlantis — Issuer ist entweder Atlantis selbst oder das dahinterliegende IDP. Scope per Projekt-IAM-Rolle, nicht eine gemeinsame „atlantis-runner“-Rolle.
Die Trust Policy ist bis auf Issuer und Sub-Pattern identisch:
resource "aws_iam_role" "ci_deploy" {
assume_role_policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = "sts:AssumeRoleWithWebIdentity"
Principal = { Federated = "arn:aws:iam::${var.acc}:oidc-provider/${var.issuer}" }
Condition = {
StringEquals = { "${var.issuer}:aud" = "sts.amazonaws.com" }
StringLike = { "${var.issuer}:sub" = var.sub_pattern }
}
}]
})
}
Was Sie auch dann nicht tun dürfen, wenn es „läuft“
Drei Antimuster tauchen in öffentlichen Repositories immer wieder auf und neutralisieren den Gewinn aus OIDC:
AdministratorAccessan der OIDC-Rolle „für die Demo“. Eine föderierte Rolle muss least-privilege sein. Andernfalls erhält ein Angreifer bei kompromittiertem Build-Skript (npm-Abhängigkeit, Supply-Chain-Hop) exakt das, was Ihriam-userihm zuvor gab — nur mit besserer Attribution. Kein Gewinn.- Ein Job führt
npm buildundterraform applyaus. Der Build läuft auf einem Default-Runner, kann beliebige Abhängigkeiten ziehen, und derselbe Prozess erhält danach ein temporäres Token mit Prod-Rechten. Die richtige Form: ein Build-Job assumiert keine Rolle und schiebt das Artefakt in eine Registry, ein separater Apply-Job ruftconfigure-aws-credentialsauf. Eine kompromittierte Abhängigkeit reicht nur bis zu den Pull-Rechten. AWS_ACCESS_KEY_IDim Runner-Environment lebt „aus Kompatibilität“ weiter. Environment-Variablen überschreibenAWS_PROFILEin der aws-CLI; ein vergessenesAWS_ACCESS_KEY_ID=…in Group-Level-Variablen umgeht die OIDC-Session stillschweigend. Vorterraformin der CI-Shell immerunset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN.
Eine zusätzliche Falle für Terraform: module { source = "s3::…" } respektiert AWS_PROFILE nicht — Module werden mit den Default-Credentials geladen. Zwei Korrekturen: vor terraform init aws configure export-credentials --format env ausführen, oder Module in Git/Registry statt in S3 halten.
Was Sie aufhören werden zu tun
Nach der Migration verschwindet die Routine: Schlüsselrotation, Reviews darüber, wer wann AWS_* in secrets abgelegt hat, das Off-Boarding ehemaliger Teammitglieder mit Schlüsseln auf Laptops. Die Zahl der dauerhaften Geheimnisse im CI fällt auf null. Die Angriffsfläche von CI/CD schrumpft auf das Fälschen der JWT-Signatur von GitHub oder GitLab — was im realistischen Bedrohungsmodell der Kompromittierung des Anbieters selbst gleichkommt.
Das ist die seltene Security-Migration, die Verfahren entfernt statt sie hinzuzufügen.