IAM: User, Role und Policy richtig nutzen
1. Überblick
IAM (Identity and Access Management) ist ein Basisdienst von AWS, der entscheidet, wer was mit welchen Ressourcen tun darf. Viele Teams nutzen IAM von Anfang an falsch: Sie erstellen User mit Admin-Rechten für alles, ordnen schnell Policies mit *:* zu, oder verwechseln User und Role. Dieser Artikel hilft dir, die drei Konzepte User, Role und Policy klar zu unterscheiden, JSON-Policies korrekt zu schreiben, das Prinzip der Least Privilege zu befolgen und Role-Annahmen mit dem AWS SDK für Java zu testen.
Nach der Lektüre wirst du selbstsicher ein schlankes, sicheres IAM-System entwerfen können, ohne jedem Service „God-Mode”-Rechte zu geben.
2. Drei zentrale Komponenten
IAM dreht sich um drei Konzepte:
- User: Eine Identität für eine spezifische Person oder Anwendung mit langfristigen Credentials (Passwort, Access Key).
- Role: Eine temporäre Identität ohne feste Credentials, die von einem User, Service oder anderen Account angenommen (assume) wird.
- Policy: Ein JSON-Dokument, das Rechte (Allow/Deny) beschreibt und an User, Roles, Groups oder Ressourcen gebunden wird.
Hinweis: Eine Role ist nicht einfach „ein User ohne Passwort”. Roles erzeugen temporäre Credentials (zeitlich begrenzt, über STS), während User feste Credentials verwenden, bis du diese rotierst.
3. User: Wann du sie verwendest, wann nicht
User passen zu:
- Personen, die sich in der Console einloggen müssen, um manuelle Arbeiten durchzuführen.
- Notwendige Fälle, in denen Roles nicht möglich sind (selten, z. B. Legacy-Systeme außerhalb von AWS, die einen festen Access Key brauchen).
User passen nicht zu:
- Workloads, die auf EC2, Lambda, ECS laufen – dort solltest du Roles verwenden.
- Das Teilen eines User mit mehreren Personen (du verlierst die Fähigkeit, in CloudTrail zu auditen).
User über CLI erstellen:
aws iam create-user --user-name order-service-deployer
aws iam create-login-profile \
--user-name order-service-deployer \
--password 'TempPass123!' \
--password-reset-required
Die erste Zeile erstellt den User. Die create-login-profile-Zeile gewährt Console-Zugriff mit einem temporären Passwort und erzwingt eine Änderung beim ersten Anmelden – verhindert, dass Passwörter zu lange bestehen.
Häufiger Fehler: Access Keys für User erstellen, diese in den Code hard-coden und in Git committen. Falls du Access Keys verwenden musst, aktiviere regelmäßige Rotation und nutze Secrets Manager zur Speicherung.
4. Role: Temporäre Identitäten am richtigen Ort
4.1. EC2-Instance-Role
Wenn eine EC2-Instance S3 aufrufen muss, binde keinen Access Key in .env ein. Erstelle stattdessen eine Role und ordne ein Instance-Profil zu:
aws iam create-role \
--role-name OrderServiceEc2Role \
--assume-role-policy-document file://trust-policy.json
Die Datei trust-policy.json definiert wer diese Role annehmen darf:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
Principal.Service spezifiziert, dass der EC2-Service diese Role über ein Instance-Profil annehmen darf. Dies heißt Trust Policy – anders als die Permission Policy, die wir später behandeln.
4.2. Cross-Account-Role
Roles ermöglichen auch anderen AWS-Accounts, auf deinen Account zuzugreifen, ohne Credentials zu teilen:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "order-partner-2024" }
}
}
]
}
Condition mit sts:ExternalId schützt vor Confused Deputy Attacks – ein Dritter muss die geheime ExternalId kennen, um die Role anzunehmen, und verhindert, dass ein Vierter die öffentliche ARN ausnutzt.
5. Policy: Struktur und Schreibweise
Eine JSON-Policy besteht aus Version und Statement – jede Anweisung hat Effect, Action, Resource und optional Condition.
Beispiel: Erlaube PaymentService, einen bestimmten S3-Bucket zu lesen und zu schreiben, aber nicht zu löschen:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadWritePaymentBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::payment-receipts-bucket/*"
},
{
"Sid": "DenyDeleteObject",
"Effect": "Deny",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::payment-receipts-bucket/*"
}
]
}
Die erste Anweisung Allow erteilt GetObject und PutObject nur für den Pfad payment-receipts-bucket/*. Die zweite Anweisung Deny lehnt das Löschen explizit ab – selbst wenn später eine Policy s3:* hinzugefügt wird, bleibt das explizite Deny wirksam.
Diese Policy an eine Role binden:
aws iam put-role-policy \
--role-name PaymentServiceRole \
--policy-name PaymentBucketAccess \
--policy-document file://payment-bucket-policy.json
5.1. Identity-basierte versus ressourcenbasierte Policies
- Identity-basierte Policy: An User/Role/Group gebunden, beantwortet „was darf diese Identität tun”.
- Ressourcenbasierte Policy: Direkt an eine Ressource gebunden (S3-Bucket-Policy, SQS-Queue-Policy, KMS-Key-Policy), beantwortet „wer darf auf diese Ressource zugreifen”.
Beispiel einer Bucket-Policy (ressourcenbasiert), die nur einer bestimmten Role Zugriff erlaubt:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/PaymentServiceRole"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::payment-receipts-bucket/*"
}
]
}
Diese zwei Policy-Typen können kombiniert werden – die endgültige Berechtigung ist der Schnittmenge beider (sowohl Identity-basiert als auch ressourcenbasiert müssen Allow geben; wenn eine Deny setzt, ist die Aktion blockiert, außer bei Cross-Account, da müssen beide explizit Allow sein).
6. Prinzip der Least Privilege
Statt schnell "Action": "s3:*" zu schreiben, liste die genauen Actions auf. Nutze Condition, um den Umfang weiter einzuschränken.
Beispiel: Erlaube das Lesen von Objekten nur von einem bestimmten VPC:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::order-exports-bucket/*",
"Condition": {
"StringEquals": {
"aws:SourceVpc": "vpc-0abc1234"
}
}
}
]
}
aws:SourceVpc ist ein globaler Condition-Schlüssel und erteilt Allow nur für Anfragen vom VPC-Endpoint des angegebenen VPC – nützlich, wenn Daten das interne Netzwerk nicht verlassen sollen.
Praktischer Ansatz für Least Privilege:
- Starte mit einer leeren Policy (Standard-Deny).
- Führe die Workload aus und beobachte in IAM Access Analyzer oder CloudTrail, welche Actions abgelehnt werden.
- Füge nur diese Actions zur Policy hinzu, ohne Extras.
- Wiederhole, bis die Workload stabil läuft, ohne blockiert zu werden.
Hinweis: Nutze
aws iam generate-service-last-accessed-details, um zu sehen, welche Actions ein User/eine Role in den letzten 90 Tagen tatsächlich verwendet hat – so kannst du überschüssige Actions aus bestehenden Policies entfernen.
7. Policy-Evaluierungslogik, die du kennen musst
AWS bewertet Berechtigungen in dieser Reihenfolge – du solltest sie kennen, um schnell zu debuggen:
- Standardmäßig werden alle deny (implizites Deny).
- Gibt es eine Anweisung mit explizitem
Deny→ das Endergebnis ist deny, nichts kann das retten. - Ohne explizites Deny suchst du eine passende
Allow-Anweisung → allow. - Wenn keine Allow-Anweisung passt → trotzdem deny (implizites Standard-Deny).
Anfrage kommt → Gibt es ein passendes explizites Deny?
├─ Ja → DENY (Ende)
└─ Nein → Gibt es ein passendes Allow?
├─ Ja → ALLOW
└─ Nein → DENY (implizit)
Das Verständnis dieser Reihenfolge hilft dir, den klassischen Bug zu vermeiden: Du bindest AdministratorAccess an einen User, wirst aber bei einer Action blockiert, weil eine SCP (Service Control Policy) auf Organisationsebene diese Action explizit deny setzt.
8. Role-Annahme mit AWS SDK für Java testen
SDK v2-Abhängigkeit hinzufügen:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
<version>2.28.11</version>
</dependency>
Der Service ruft STS auf, um die Role anzunehmen und temporäre Credentials zu holen:
public class RoleAssumer {
private final StsClient stsClient;
public RoleAssumer(StsClient stsClient) {
this.stsClient = stsClient;
}
public Credentials assumePaymentServiceRole() {
AssumeRoleRequest request = AssumeRoleRequest.builder()
.roleArn("arn:aws:iam::123456789012:role/PaymentServiceRole")
.roleSessionName("payment-service-session")
.durationSeconds(900)
.build();
AssumeRoleResponse response = stsClient.assumeRole(request);
return response.credentials();
}
}
roleArn spezifiziert die anzunehmende Role, roleSessionName benennt die Sitzung für einfacheres CloudTrail-Tracking, durationSeconds begrenzt die Lebensdauer der temporären Credentials – je kürzer, desto sicherer.
Test mit JUnit 5 und Mockito, um zu überprüfen, dass die richtige Role-ARN aufgerufen wird, ohne AWS tatsächlich zu kontaktieren:
@Test
void assumePaymentServiceRole_shouldRequestCorrectRoleArn() {
StsClient stsClient = mock(StsClient.class);
Credentials fakeCredentials = Credentials.builder()
.accessKeyId("AKIAFAKE")
.secretAccessKey("secretFake")
.sessionToken("tokenFake")
.build();
when(stsClient.assumeRole(any(AssumeRoleRequest.class)))
.thenReturn(AssumeRoleResponse.builder()
.credentials(fakeCredentials)
.build());
RoleAssumer assumer = new RoleAssumer(stsClient);
Credentials result = assumer.assumePaymentServiceRole();
ArgumentCaptor<AssumeRoleRequest> captor = ArgumentCaptor.forClass(AssumeRoleRequest.class);
verify(stsClient).assumeRole(captor.capture());
assertThat(captor.getValue().roleArn())
.isEqualTo("arn:aws:iam::123456789012:role/PaymentServiceRole");
assertThat(result.accessKeyId()).isEqualTo("AKIAFAKE");
}
Dieser Test mockt StsClient, erfasst die tatsächlich gesendete Anfrage und prüft, ob roleArn wie erwartet ist. So erkennst du früh, wenn jemand versehentlich die hard-codierte Role-ARN im Code ändert.
assumePaymentServiceRole_shouldRequestCorrectRoleArn() PASSED
9. Häufige Fehler, die du vermeiden solltest
- Die Policy
AdministratorAccessfür alle Service-Accounts vergeben mit der Entschuldigung „später optimieren”. In der Praxis optimiert niemand das, das Risiko bleibt auf Dauer. - Access Keys für Workloads auf EC2/Lambda statt Roles verwenden – Access Keys leaken leicht, rotieren nicht automatisch und haben kein Ablaufdatum.
- Vergessen, die Trust Policy der Role zu überprüfen:
Principalist zu weit gefasst (z. B."AWS": "*"), jeder kann die Role annehmen. - Keine
Conditionmit MFA für sensible Aktionen verwenden (Bucket löschen, IAM-Policy ändern). Füge"Bool": {"aws:MultiFactorAuthPresent": "true"}für hochriskante Actions hinzu. - Viele Verantwortungen in eine große Policy mischen, was das Review schwierig macht. Teile stattdessen nach Ressource/Service auf und gib klare Namen (
PaymentBucketReadWrite, nichtMyPolicy1).
Wenn du komplexe IAM nicht brauchst: Wenn du an einer persönlichen Sandbox arbeitest oder einen neuen Service lernst, kann es das Lerntempo verlangsamen, wenn du detaillierte Roles/Policies von Anfang an erstellst. Nutze dann einen temporären User mit vorgefertigten Managed Policies (wie
AmazonS3ReadOnlyAccess), nur denk dran, ihn danach zu löschen.
10. Fazit
User sind für Personen gedacht, die sich manuell einloggen müssen, Roles für Workloads und temporäre Zugriffe, Policies sind JSON-Regeln, die Allow/Deny festlegen. Wenn du die Evaluierungsreihenfolge verstehst (explizites Deny gewinnt immer, implizites Deny ist Standard), kannst du schnell debuggen, wenn merkwürdige Berechtigungen auftreten. Wende Least Privilege an, indem du von leer anfängst, basierend auf echten Logs hinzufügst und immer Tests für deine Role-Annahme-Logik schreibst – so hältst du dein AWS-System sicher, ohne später viel Wartungsaufwand zu haben.