Oktober 2026
M D M D F S S
 1234
567891011
12131415161718
19202122232425
262728293031  

Dein Terraform State liegt verschlüsselt in einem S3-Bucket, hinter einem Remote Backend, mit Locking über eine Lock-Datei oder Lock-Table. Damit ist die Schreibkonkurrenz gelöst. Die Frage, wer die Datei überhaupt lesen darf, ist damit noch nicht beantwortet.

„Terraform State absichern: Remote Backend, Locking und Secrets“ (17.06.2026) behandelt Backend-Einrichtung und Locking-Mechanik. Dieser Beitrag setzt an einer anderen Stelle an: der Zugriffskontrolle auf den Speicherort selbst, also wer lesend oder schreibend an die Rohdatei kommt, in der dein State liegt.

Was im State steht, und warum das relevant ist

Terraform speichert im State nicht nur, welche Ressourcen existieren. Er enthält auch Attribute dieser Ressourcen, inklusive solcher, die Terraform als sensibel markiert, und inklusive Output-Werte. Legst du einen Output ohne sensitive = true an, etwa ein generiertes Datenbankpasswort oder einen privaten Schlüssel, steht dieser Wert im Klartext in der State-Datei.

Jeder, der lesenden Zugriff auf den Speicherort hat, egal ob S3-Bucket, Azure-Storage-Account oder GCS-Bucket, kann diese Werte extrahieren. Das geht ohne Terraform-CLI, allein durch Herunterladen der Datei. Wer stattdessen Terraform-Tooling nutzt, bekommt es noch bequemer:

terraform output -json

Dieser Befehl gibt alle Outputs inklusive der als sensitive markierten im Klartext als JSON zurück, wenn der Aufrufer Lesezugriff auf den State hat. Die sensitive-Markierung verhindert nur die Anzeige im normalen Plan/Apply-Output, keinen Zugriff über -json oder über den rohen State.

Verschlüsselung at rest (zum Beispiel SSE-S3 oder SSE-KMS auf dem Bucket) schützt gegen physischen Diebstahl des Speichermediums oder unautorisierten Zugriff außerhalb von AWS. Sie schützt nicht gegen eine IAM-Identität, die legitim lesen darf. Genau dort setzt Zugriffskontrolle an.

Zugriffskontrolle auf den Speicherort: das eigentliche Problem

Ein Terraform-Backend braucht fast immer mindestens s3:GetObject und s3:PutObject auf den State-Pfad, dazu s3:ListBucket für den Bucket. Die übliche Falle: Diese Rechte werden auf Kontoebene oder über eine breite Policy an viele Rollen vergeben, weil mehrere Pipelines, Entwickler-Laptops oder CI-Jobs irgendwann Zugriff brauchten und niemand die Policy seitdem verschlankt hat. Das Ergebnis ist ein Bucket, auf den de facto jede Rolle im Account lesend zugreifen kann, auch solche, die mit Terraform nichts zu tun haben.

Das Prinzip der geringsten Rechte verlangt hier zweierlei: erstens den Zugriff auf genau die Rollen zu beschränken, die den State tatsächlich lesen oder schreiben müssen (CI-Rolle, ggf. eine begrenzte Admin-Rolle für Troubleshooting), zweitens den Zugriff pro State-Pfad zu trennen, wenn mehrere Teams oder Umgebungen denselben Bucket nutzen.

Beispiel: eine restriktive S3-Bucket-Policy

Die folgende Policy lässt nur eine bestimmte IAM-Rolle auf einen bestimmten State-Pfad zu und blockiert explizit unverschlüsselte Übertragung:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowTerraformRoleOnlyOnStatePath",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::123456789012:role/terraform-ci-role" },
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-terraform-state-bucket/prod/network/terraform.tfstate"
},
{
"Sid": "AllowListOnlyForTerraformRole",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::123456789012:role/terraform-ci-role" },
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-terraform-state-bucket",
"Condition": { "StringLike": { "s3:prefix": "prod/network/*" } }
},
{
"Sid": "DenyUnencryptedTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::my-terraform-state-bucket", "arn:aws:s3:::my-terraform-state-bucket/*"],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
},
{
"Sid": "DenyEveryoneElse",
"Effect": "Deny",
"NotPrincipal": { "AWS": ["arn:aws:iam::123456789012:role/terraform-ci-role", "arn:aws:iam::123456789012:root"] },
"Action": "s3:*",
"Resource": ["arn:aws:s3:::my-terraform-state-bucket", "arn:aws:s3:::my-terraform-state-bucket/*"]
}
]
}

Der DenyEveryoneElse-Block ist der eigentliche Unterschied zu den meisten Setups: Ohne ihn reicht eine zusätzliche, später ergänzte Allow-Policy irgendwo im Account, um den Zugriff wieder zu öffnen. Mit ihm bleibt der Bucket auch gegen neue, großzügig formulierte IAM-Policies abgedichtet, solange die Rollen-ARNs in NotPrincipal aktuell gehalten werden. Zwei Einschränkungen gehören dazu: Ein expliziter Deny mit NotPrincipal wirkt kontoübergreifend nur dann wie gewollt, wenn der Root-Account und alle legitim zugreifenden Rollen explizit in der Ausnahmeliste stehen, sonst sperrt der Block auch berechtigte kontoübergreifende Zugriffe aus; läuft der Account zusätzlich unter einer AWS-Organizations-SCP, bleibt die SCP eine eigene, vorgelagerte Prüfung, die diese Bucket-Policy nicht ersetzt.

Azure und GCS: gleiches Prinzip, andere Werkzeuge

Bei Azure übernimmt RBAC auf dem Storage Account diese Rolle: Zugriff auf den Blob-Container mit dem State wird über eine zugewiesene Rolle wie „Storage Blob Data Contributor“ auf einzelne Service Principals begrenzt, nicht auf die gesamte Subscription vergeben.

Bei Google Cloud Storage läuft das über Bucket-IAM-Bindings, idealerweise mit einer eigenen, auf den Bucket beschränkten Bindung einer Rolle wie roles/storage.objectAdmin statt projektweiter Vergabe. In allen drei Fällen gilt derselbe Test: Welche Identität kann die Rohdatei lesen, ohne dass du das aktiv erlaubt hast?

Was dieser Beitrag nicht abdeckt

Dieser Post klärt, wer an die gespeicherten Daten kommt. Er klärt nicht, wie das Backend eingerichtet wird oder wie konkurrierende Schreibzugriffe verhindert werden, dafür bleibt „Terraform State absichern“ die richtige Anlaufstelle.

Fazit

Ein verschlüsselter State in einem Bucket mit sauberem Locking ist noch kein State mit Zugriffskontrolle. Prüfe, welche Rollen tatsächlich GetObject auf deinen State-Pfad dürfen, und ob jede dieser Rollen diesen Zugriff wirklich braucht. Markiere als sensibel, was sensibel ist, und geh davon aus, dass terraform output -json für jede lesende Identität funktioniert.

Fragen zur Zugriffskontrolle auf deinen Terraform-State-Speicherort: Schreib mir an mm@mhm-dl.de.

Über das Anmeldeformular für den Newsletter trägst du deine E-Mail-Adresse ein, kreuzt die Einwilligung an und bestätigst die Anmeldung danach per Mail. Mit der Anmeldung willigst du ein, dass der Newsletter unter anderem folgende Themen behandelt: Schulungen zu Docker, Kubernetes, CI/CD, Git-Workflows und DevSecOps-Werkzeugen sowie die Anforderungen aus NIS2, CRA und DORA und deren technische Umsetzung.

Kommentar verfassen

Diese Seite verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden..