AWS IAM’de MFA Zorunlu Kılma: Adım Adım Güvenlik Rehberi
AWS hesabınıza yetkisiz erişim yaşandığında bunu genellikle fatura alarmlarından ya da CloudTrail loglarındaki tuhaf API çağrılarından anlarsınız. O noktada iş işten geçmiş olur. MFA (Multi-Factor Authentication) zorunlu kılmak, bu senaryonun önüne geçmenin en temel ve en etkili yollarından biri. Ancak “MFA açık mı?” sorusunun cevabı çoğu zaman “bazı kullanıcılarda var” oluyor; bu da yeterli değil. Bu yazıda AWS IAM’de MFA’yı gerçek anlamda zorunlu kılmayı, policy’leri nasıl yazacağınızı, otomasyon tarafını ve sık karşılaşılan tuzakları konuşacağız.
MFA Zorunlu Kılma Neden Bu Kadar Önemli?
Bir IAM kullanıcısının erişim anahtarı ya da şifresi ele geçirildiğinde, saldırgan hesabınıza saniyeler içinde giriş yapabilir. 2022’deki büyük bulut ihlallerinin önemli bir kısmında ortak nokta şuydu: MFA yoktu ya da sadece bazı kullanıcılarda vardı.
AWS’de MFA’nın “aktif” olması ile “zorunlu” olması arasında kritik bir fark var. Kullanıcı MFA cihazını tanımlamış olabilir ama MFA olmadan da giriş yapıp API çağrısı yapabilir. Gerçek anlamda zorunlu kılmak için IAM policy seviyesinde bunu enforce etmeniz gerekiyor.
Şunu da belirtmek gerekir: root hesabı için MFA ayrı bir konu. Root hesabına mutlaka hardware MFA ya da en azından sanal MFA tanımlamalısınız ve bu hesabı günlük operasyonlarda hiç kullanmamalısınız.
Temel Senaryo: “MFA Yoksa Hiçbir Şey Yapamaz” Policy’si
En yaygın ve pratik yaklaşım şu: Kullanıcı giriş yapar, MFA doğrulaması yapmazsa sadece kendi MFA ayarlarını düzenleyebilir, başka hiçbir işlem yapamaz.
Bunu sağlayan IAM policy şöyle görünür:
cat << 'EOF' > force-mfa-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowViewAccountInfo",
"Effect": "Allow",
"Action": [
"iam:GetAccountPasswordPolicy",
"iam:ListVirtualMFADevices"
],
"Resource": "*"
},
{
"Sid": "AllowManageOwnPasswords",
"Effect": "Allow",
"Action": [
"iam:ChangePassword",
"iam:GetUser"
],
"Resource": "arn:aws:iam::*:user/${aws:username}"
},
{
"Sid": "AllowManageOwnMFADevice",
"Effect": "Allow",
"Action": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ResyncMFADevice"
],
"Resource": [
"arn:aws:iam::*:mfa/${aws:username}",
"arn:aws:iam::*:user/${aws:username}"
]
},
{
"Sid": "DenyAllExceptListedIfNoMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ListVirtualMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
EOF
Bu policy’de dikkat çeken nokta BoolIfExists kullanımı. Sadece Bool kullansaydınız, MFA condition’ı hiç değerlendirmeyen long-term credential’lar (access key ile doğrudan API çağrıları) bu deny’dan kaçabilirdi. BoolIfExists, condition key mevcut değilse “false” varsayar, bu da policy’yi daha sıkı hale getirir.
Policy’yi Oluşturup Gruba Bağlama
Policy’yi AWS CLI ile oluşturalım ve ilgili gruba ekleyelim:
# Policy'yi oluştur
aws iam create-policy
--policy-name ForceMFAPolicy
--policy-document file://force-mfa-policy.json
--description "MFA olmadan erisimi engelleyen policy"
# Hesap ID'nizi alın
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Policy ARN'i
POLICY_ARN="arn:aws:iam::${ACCOUNT_ID}:policy/ForceMFAPolicy"
# Yeni grup oluşturun (ya da mevcut gruba ekleyin)
aws iam create-group --group-name AllUsers
# Policy'yi gruba bağlayın
aws iam attach-group-policy
--group-name AllUsers
--policy-arn ${POLICY_ARN}
echo "Policy basariyla gruba baglandi."
Tüm IAM kullanıcılarını bu gruba eklemeniz gerekiyor. Mevcut kullanıcıları toplu olarak eklemek için:
# Mevcut tüm IAM kullanıcılarını listele ve gruba ekle
aws iam list-users --query 'Users[*].UserName' --output text |
tr 't' 'n' |
while read username; do
echo "Kullanici ekleniyor: $username"
aws iam add-user-to-group
--user-name "$username"
--group-name AllUsers
done
MFA Durumunu Kontrol Etme
Policy’yi uyguladıktan sonra hangi kullanıcıların MFA’sı var, hangilerinde yok, bunu düzenli kontrol etmeniz gerekiyor.
#!/bin/bash
# mfa-audit.sh - MFA durumunu denetleyen script
echo "=== IAM MFA Denetim Raporu ==="
echo "Tarih: $(date)"
echo ""
echo "--- MFA'si OLMAYAN kullanicilar ---"
aws iam list-users --query 'Users[*].UserName' --output text |
tr 't' 'n' |
while read username; do
mfa_count=$(aws iam list-mfa-devices
--user-name "$username"
--query 'length(MFADevices)'
--output text)
if [ "$mfa_count" -eq "0" ]; then
# Son login zamanini da alalim
last_used=$(aws iam get-user
--user-name "$username"
--query 'User.PasswordLastUsed'
--output text 2>/dev/null || echo "Hic giris yapilmamis")
echo "Kullanici: $username | Son giris: $last_used"
fi
done
echo ""
echo "--- MFA'si OLAN kullanicilar ---"
aws iam list-users --query 'Users[*].UserName' --output text |
tr 't' 'n' |
while read username; do
mfa_devices=$(aws iam list-mfa-devices
--user-name "$username"
--query 'MFADevices[*].SerialNumber'
--output text)
if [ -n "$mfa_devices" ]; then
echo "Kullanici: $username | Cihaz: $mfa_devices"
fi
done
Bu scripti cron’a ekleyip haftalık e-posta raporu haline getirebilirsiniz ya da Slack’e gönderebilirsiniz.
Service Control Policy ile Organizasyon Genelinde Zorunlu Kılma
Birden fazla AWS hesabı yönetiyorsanız (AWS Organizations kullanıyorsanız), her hesap için ayrı ayrı bu işlemi yapmak yerine SCP (Service Control Policy) kullanabilirsiniz. SCP, Organizations hiyerarşisindeki tüm hesaplara uygulanır.
cat << 'EOF' > scp-require-mfa.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyWithoutMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ListVirtualMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken",
"iam:ChangePassword"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
EOF
# SCP'yi oluştur
aws organizations create-policy
--name "RequireMFA"
--description "Tum hesaplarda MFA zorunlu kilar"
--content file://scp-require-mfa.json
--type SERVICE_CONTROL_POLICY
# OU veya hesaba bağlama (OU ID'nizi girin)
OU_ID="ou-xxxx-xxxxxxxx"
SCP_ID=$(aws organizations list-policies
--filter SERVICE_CONTROL_POLICY
--query "Policies[?Name=='RequireMFA'].Id"
--output text)
aws organizations attach-policy
--policy-id $SCP_ID
--target-id $OU_ID
Önemli not: SCP’yi management account’a uygulamayın. Management hesabında SCP’ler çalışmaz ve yanlış yapılandırma tüm hesapları kilitleyebilir.
Gerçek Dünya Senaryosu: CI/CD Pipeline’ları ve MFA
Burada sık karşılaşılan bir sorun var. CI/CD sistemlerinde (Jenkins, GitHub Actions, GitLab CI) kullandığınız IAM kullanıcıları genellikle long-term access key kullanır ve bu kullanıcılara MFA policy’si uyguladığınızda pipeline’lar bozulur.
Çözüm yolu şu: CI/CD için IAM kullanıcısı yerine IAM Role kullanın ve bu role’e MFA condition koymayın.
# CI/CD için assume edilecek role oluşturma
cat << 'EOF' > cicd-trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_ID:user/cicd-service-user"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-buraya"
}
}
}
]
}
EOF
# Role oluştur
aws iam create-role
--role-name CICDDeployRole
--assume-role-policy-document file://cicd-trust-policy.json
--description "CI/CD pipeline deploy role"
# Service user için sadece AssumeRole yetkisi ver
cat << 'EOF' > cicd-user-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::ACCOUNT_ID:role/CICDDeployRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-buraya"
}
}
}
]
}
EOF
aws iam put-user-policy
--user-name cicd-service-user
--policy-name CICDAssumeRoleOnly
--policy-document file://cicd-user-policy.json
Bu yapıda CI/CD kullanıcısının kendi başına yapabileceği hiçbir şey yok. Sadece belirli bir role’ü assume edebilir. Anahtarlar ele geçirilse bile zarar sınırlı kalır.
AWS Config ile Uyumluluk İzleme
MFA durumunu sürekli manuel kontrol etmek yerine AWS Config rule’larıyla otomatik uyumluluk izlemesi yapabilirsiniz.
# AWS Config managed rule - MFA olmayan root hesabını tespit eder
aws configservice put-config-rule
--config-rule '{
"ConfigRuleName": "root-account-mfa-enabled",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "ROOT_ACCOUNT_MFA_ENABLED"
}
}'
# IAM kullanıcıları için MFA kontrolü
aws configservice put-config-rule
--config-rule '{
"ConfigRuleName": "iam-user-mfa-enabled",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "IAM_USER_MFA_ENABLED"
}
}'
# Uyumsuz kaynakları listeleme
aws configservice get-compliance-details-by-config-rule
--config-rule-name iam-user-mfa-enabled
--compliance-types NON_COMPLIANT
--query 'EvaluationResults[*].EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId'
--output table
Config rule uyumsuzluk tespitinde SNS üzerinden e-posta ya da PagerDuty entegrasyonu yapabilirsiniz. Birisi MFA’sını kaldırırsa veya yeni bir kullanıcı MFA olmadan oluşturulursa anında haberdar olursunuz.
Sık Yapılan Hatalar ve Çözümleri
Deny policy ile kendinizi kilitleme: Policy’yi uyguladıktan sonra mevcut session’ınızın MFA ile açılmamış olduğunu fark edersiniz ve bir anda hiçbir şey yapamaz hale gelirsiniz. Bunu önlemek için policy’yi test ortamında deneyin ve her zaman bir “kurtarma” kullanıcısı bulundurun. Bu kullanıcı sadece acil durumlarda kullanılmalı ve çok güçlü MFA korumasına sahip olmalı.
aws:MultiFactorAuthPresent yoksa ne olur: Long-term credential kullanırken (access key) bu condition key hiç set edilmez. BoolIfExists kullanmak bu durumu ele alır. Eğer sadece Bool kullansaydınız, condition key yoksa AWS policy’yi skip eder ve deny gerçekleşmez.
Console vs CLI davranış farkı: Console login için aws:MultiFactorAuthPresent session token içinde set edilir. Ancak CLI ile access key kullanıyorsanız ve sts:GetSessionToken ile MFA doğrulaması yapıp temporary credential almıyorsanız, bu condition her zaman false sayılır.
SCP ve IAM policy etkileşimi: SCP’nin “deny” demesi yeterli değil eğer IAM policy “allow” demiyorsa. Ve SCP “deny” dediğinde IAM policy’nin allow demesi de işe yaramaz. İkisi birlikte değerlendirilir. Management account’taki kullanıcılara SCP uygulanmaz, bu ayrı bir güvenlik katmanı gerektirir.
MFA Policy Test Etme
Policy’yi canlıya almadan önce mutlaka test etmelisiniz. AWS’nin IAM Policy Simulator aracı bunun için idealdir:
# Policy simulator ile test - MFA olmadan EC2 listesi
aws iam simulate-principal-policy
--policy-source-arn "arn:aws:iam::ACCOUNT_ID:user/test-user"
--action-names "ec2:DescribeInstances"
--context-entries '[
{
"ContextKeyName": "aws:MultiFactorAuthPresent",
"ContextKeyValues": ["false"],
"ContextKeyType": "boolean"
}
]'
--query 'EvaluationResults[*].{Action:EvalActionName,Decision:EvalDecision}'
# MFA varken test
aws iam simulate-principal-policy
--policy-source-arn "arn:aws:iam::ACCOUNT_ID:user/test-user"
--action-names "ec2:DescribeInstances"
--context-entries '[
{
"ContextKeyName": "aws:MultiFactorAuthPresent",
"ContextKeyValues": ["true"],
"ContextKeyType": "boolean"
}
]'
--query 'EvaluationResults[*].{Action:EvalActionName,Decision:EvalDecision}'
İlk komut “explicitDeny” döndürmeli, ikincisi “allowed”. Bu çıktıları görürseniz policy doğru çalışıyor demektir.
Hardware MFA vs. Sanal MFA
Güvenlik gereksinimlerinize göre MFA tipini de kısıtlayabilirsiniz. Kritik roller için FIDO2/hardware MFA şart koşmak isteyebilirsiniz:
# Sadece hardware MFA olan oturumlar için policy condition
cat << 'EOF' > hardware-mfa-only.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "iam:DeleteVirtualMFADevice",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
},
{
"Sid": "RequireHardwareMFAForSensitiveActions",
"Effect": "Deny",
"Action": [
"iam:*",
"organizations:*",
"account:*"
],
"Resource": "*",
"Condition": {
"NumericGreaterThanIfExists": {
"aws:MultiFactorAuthAge": "3600"
}
}
}
]
}
EOF
aws:MultiFactorAuthAge condition key’i, MFA doğrulamasının üzerinden kaç saniye geçtiğini söyler. Hassas işlemler için bu süreyi kısa tutarsanız kullanıcılar belirli aralıklarla yeniden MFA doğrulaması yapmak zorunda kalır.
Kullanıcı Deneyimi Tarafı
Teknik taraf tamam olsa da kullanıcıları bilgilendirmeden bu policy’yi devreye aldığınızda şikayetler başlar. Şu adımları izlemenizi öneririm:
- Önce pilot gruba uygulayın, bir hafta gözlemleyin
- Kullanıcılara MFA kurulum rehberi gönderin (Google Authenticator, Microsoft Authenticator veya Authy)
- Policy’yi devreye almadan önce MFA’sı olmayan kullanıcılara hatırlatma gönderin
- “Grace period” oluşturun: İlk hafta sadece uyarı, sonraki hafta zorunlu kılma
- Helpdesk’i hazırlayın; MFA cihazı kaybeden kullanıcıların durumu çözülmeli
MFA cihazı kaybeden kullanıcı için admin müdahalesi gerekir:
# MFA cihazını devre dışı bırakma (cihaz kaybolursa admin yapar)
MFA_SERIAL=$(aws iam list-mfa-devices
--user-name etkilenen-kullanici
--query 'MFADevices[0].SerialNumber'
--output text)
aws iam deactivate-mfa-device
--user-name etkilenen-kullanici
--serial-number $MFA_SERIAL
# Sanal MFA cihazını da sil
aws iam delete-virtual-mfa-device
--serial-number $MFA_SERIAL
echo "MFA kaldirildi. Kullanici yeni MFA tanimlayabilir."
Bu işlemi yapan admin’in kendisinin de MFA ile oturum açmış olduğundan emin olun ve bu tür işlemleri CloudTrail’de izleyin.
Sonuç
AWS IAM’de MFA zorunlu kılmak tek bir policy eklemekten ibaret değil. Doğru condition key seçimi (BoolIfExists), CI/CD gibi özel durumların ele alınması, organizasyon genelinde SCP uygulaması ve düzenli denetim, bu işin tam anlamıyla yapılması için hepsini bir arada düşünmeniz gerekiyor.
En kritik noktaları özetlersem: BoolIfExists kullanmayı unutmayın, CI/CD kullanıcılarını IAM Role’e geçirin, Config rule’larıyla uyumluluğu otomatik izleyin ve kendinizi kilitlememeye dikkat edin. Root hesabı için MFA ayrı bir öncelik ve bunu hiç ertelememelisiniz.
Küçük bir AWS hesabı da yönetseniz, onlarca hesaplı bir organization da yönetseniz, MFA zorunlu kılmanın maliyeti sıfıra yakın ama getirisi çok büyük. Bir güvenlik ihlalini temizlemenin maliyeti, bu yapılandırmaya harcayacağınız birkaç saatin çok çok üzerinde.
