AWS S3 Bucket Politikaları ve İzin Yönetimi
AWS’de yanlış yapılandırılmış bir S3 bucket’ı, dakikalar içinde milyonlarca kullanıcının verisini ifşa edebilir. Geçmişte yaşanan büyük veri sızıntılarının önemli bir kısmının arkasında “herkese açık” bırakılmış S3 bucket’ları var. Bu yüzden S3 izin modelini gerçekten anlamak, sadece bir “best practice” meselesi değil, doğrudan iş sürekliliği ve güvenlik meselesi.
Bu yazıda S3’ün izin katmanlarını, bucket policy yazımını, IAM entegrasyonunu ve gerçek dünya senaryolarını ele alacağız. Teori değil, elle tutulur örnekler üzerinden gideceğiz.
S3 İzin Modeli: Katmanları Anlamak
S3’te erişim kontrolü tek bir yerden değil, birden fazla katmandan yönetilir. Bu katmanları kafanızda netleştirmezseniz, neden bir şeyin çalışıp çalışmadığını anlamak gerçekten zor olabiliyor.
Erişim kontrol katmanları şunlar:
- IAM Politikaları: Kullanıcıya, role veya gruba bağlı. “Bu kimlik ne yapabilir?” sorusuna cevap verir.
- Bucket Politikaları: Bucket’a bağlı kaynak tabanlı politikalar. “Bu bucket’a kim erişebilir?” sorusunu yanıtlar.
- ACL (Access Control Lists): Eski nesil yöntem, mümkün olduğunca kaçının. AWS artık bunu aktif olarak önermez.
- Block Public Access Ayarları: Hesap veya bucket seviyesinde kamusal erişimi engelleyen bir güvenlik ağı.
- VPC Endpoint Politikaları: Trafik VPC üzerinden geliyorsa devreye girer.
AWS’nin değerlendirme mantığı şu şekilde işler: Önce “explicit deny” var mı diye bakar. Varsa iş biter, erişim reddedilir. Yoksa tüm politikalarda “allow” var mı diye kontrol eder. Hem IAM hem de bucket policy varsa, her ikisinde de izin bulunması gerekir (cross-account senaryoları için).
Block Public Access: İlk Savunma Hattı
Yeni bir bucket oluşturduğunuzda AWS artık varsayılan olarak tüm kamusal erişimi bloke eder. Bu ayarı kapatmadan önce gerçekten kapatmanız gerekip gerekmediğini sorgulayın.
# Belirli bir bucket için Block Public Access durumunu kontrol etmek
aws s3api get-public-access-block
--bucket benim-kritik-bucket
# Çıktı örneği:
# {
# "PublicAccessBlockConfiguration": {
# "BlockPublicAcls": true,
# "IgnorePublicAcls": true,
# "BlockPublicPolicy": true,
# "RestrictPublicBuckets": true
# }
# }
Bu dört parametrenin ne işe yaradığını bilmek şart:
- BlockPublicAcls: Yeni public ACL eklenmesini engeller, mevcut public ACL’leri de görmezden gelir.
- IgnorePublicAcls: Mevcut tüm public ACL’leri yoksayar.
- BlockPublicPolicy: Public erişim veren bucket policy eklenmesini önler.
- RestrictPublicBuckets: Mevcut public policy’leri bile etkisiz hale getirir.
Statik website hosting gibi gerçekten kamusal erişim gereken durumlar dışında bu ayarları kapalı bırakmayın.
# Hesap seviyesinde tüm bucket'lar için public access'i bloke etmek
aws s3control put-public-access-block
--account-id 123456789012
--public-access-block-configuration
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Bucket Politikası Anatomisi
Bucket policy’ler JSON formatında yazılır ve direkt olarak bucket’a eklenir. Bir policy’nin temel yapısını inceleyelim:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSpecificIAMRole",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/uygulama-sunucu-rol"
},
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::benim-uygulama-bucket/*"
]
}
]
}
Her Statement bloğundaki alanlar:
- Sid: Statement ID, opsiyonel ama açıklayıcı isimler koymak yönetimi kolaylaştırır.
- Effect: Allow veya Deny. Deny her zaman Allow’u geçersiz kılar.
- Principal: Kimin erişeceği. IAM user, role, servis veya “*” (herkese açık).
- Action: Hangi S3 işlemleri. “s3:*” tüm işlemlere izin verir, dikkatli kullanın.
- Resource: Hangi bucket ve/veya object’ler. ARN formatında yazılır.
- Condition: Opsiyonel, koşullu erişim için kullanılır.
Gerçek Dünya Senaryoları
Senaryo 1: Uygulama Sunucusu Sadece Kendi Klasörüne Yazsın
Çok katmanlı bir uygulamanız var. Her servis kendi prefix’ine yazabilmeli, başkasının alanına karışmamalı.
# Önce mevcut bucket policy'yi kontrol edelim
aws s3api get-bucket-policy
--bucket production-assets
--query Policy
--output text | python3 -m json.tool
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "UygulamaAServisIzni",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/servis-a-rol"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::production-assets/servis-a/*"
},
{
"Sid": "UygulamaBServisIzni",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/servis-b-rol"
},
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::production-assets/servis-b/*"
},
{
"Sid": "ListIzniHerIkisiIcin",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::123456789012:role/servis-a-rol",
"arn:aws:iam::123456789012:role/servis-b-rol"
]
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::production-assets",
"Condition": {
"StringLike": {
"s3:prefix": [
"servis-a/*",
"servis-b/*"
]
}
}
}
]
}
Dikkat edilmesi gereken bir nokta: s3:ListBucket bucket’ın kendisine (ARN’da / olmadan), s3:GetObject ve s3:PutObject ise object’lere (ARN’da / ile) uygulanır.
Senaryo 2: Cross-Account Erişim
Farklı AWS hesaplarındaki kaynakların birbirine erişmesi çok yaygın bir ihtiyaç. Örneğin, merkezi bir logging bucket’ınız var ve farklı hesaplardaki uygulamalar oraya log yazıyor.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CrossAccountLogYazma",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::111111111111:root",
"arn:aws:iam::222222222222:root"
]
},
"Action": [
"s3:PutObject"
],
"Resource": "arn:aws:s3:::merkezi-log-bucket/hesap-*/logs/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
},
{
"Sid": "CrossAccountListEngelle",
"Effect": "Deny",
"Principal": {
"AWS": [
"arn:aws:iam::111111111111:root",
"arn:aws:iam::222222222222:root"
]
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::merkezi-log-bucket"
}
]
}
s3:x-amz-acl: bucket-owner-full-control koşulu önemli. Cross-account’ta bir hesap bucket’a object koyduğunda, varsayılan olarak object sahibi o hesap olur, bucket sahibi değil. Bu koşul sayesinde object’in kontrolü bucket sahibine geçer.
Senaryo 3: IP Bazlı Kısıtlama
Şirket içi bir uygulama var ve sadece ofis IP’lerinden erişilmesini istiyorsunuz:
# Bucket policy'yi güncellemek için önce dosyaya yazalım
cat > /tmp/ip-restricted-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SadeceOfisIPsindenErisim",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::ic-dokumanlar/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"203.0.113.0/24",
"198.51.100.50/32"
]
}
}
},
{
"Sid": "DigerIPlerdenEngelle",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::ic-dokumanlar",
"arn:aws:s3:::ic-dokumanlar/*"
],
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"203.0.113.0/24",
"198.51.100.50/32"
]
},
"Bool": {
"aws:ViaAWSService": "false"
}
}
}
]
}
EOF
# Policy'yi uygulayalım
aws s3api put-bucket-policy
--bucket ic-dokumanlar
--policy file:///tmp/ip-restricted-policy.json
aws:ViaAWSService: false koşulu kritik. Bunu eklemezseniz CloudFront, Lambda gibi AWS servislerinin bucket’a erişimi de engellenebilir.
Şifreleme Zorunluluğu
Üretim ortamlarında şifrelenmemiş upload’ları reddeden bir policy eklemek iyi bir pratik:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SifrelemedeniUploadEngelle",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::production-data/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
},
"Null": {
"s3:x-amz-server-side-encryption": "false"
}
}
},
{
"Sid": "HTTPSZorunlulugu",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::production-data",
"arn:aws:s3:::production-data/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
Bu iki kural birlikte şunu söylüyor: Hem HTTPS kullan, hem de KMS ile şifrele. Aksi halde upload reddedilir.
IAM Politikaları ile Birlikte Çalışmak
Bucket policy tek başına yeterli değil, IAM tarafını da doğru kurgulamak gerekiyor.
# EC2 instance'ına atanacak IAM role policy'si
cat > /tmp/s3-uygulama-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BucketListelemek",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::uygulama-data",
"Condition": {
"StringLike": {
"s3:prefix": "uploads/*"
}
}
},
{
"Sid": "ObjectIslemleri",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::uygulama-data/uploads/*"
},
{
"Sid": "KMSAnahtar",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "arn:aws:kms:eu-west-1:123456789012:key/anahtar-id"
}
]
}
EOF
# Policy oluştur
aws iam create-policy
--policy-name S3UygulamaErisimPolicyi
--policy-document file:///tmp/s3-uygulama-policy.json
# Role'e ekle
aws iam attach-role-policy
--role-name uygulama-ec2-rol
--policy-arn arn:aws:iam::123456789012:policy/S3UygulamaErisimPolicyi
Mevcut Konfigürasyonu Denetlemek
Üretimde bir şeyler ters gidince veya düzenli güvenlik denetimi yaparken kullanabileceğiniz komutlar:
#!/bin/bash
# Hesaptaki tüm bucket'ların public access durumunu kontrol eden script
echo "=== S3 Bucket Güvenlik Denetimi ==="
echo ""
BUCKET_LIST=$(aws s3api list-buckets --query 'Buckets[*].Name' --output text)
for bucket in $BUCKET_LIST; do
echo "Bucket: $bucket"
# Public access block kontrolü
PUBLIC_BLOCK=$(aws s3api get-public-access-block
--bucket "$bucket" 2>/dev/null
--query 'PublicAccessBlockConfiguration.{BPA:BlockPublicAcls,IPA:IgnorePublicAcls,BPP:BlockPublicPolicy,RPB:RestrictPublicBuckets}'
--output text)
if [ $? -ne 0 ]; then
echo " [UYARI] Public Access Block yapilandirmasi YOK!"
else
echo " Public Access Block: $PUBLIC_BLOCK"
fi
# Bucket policy var mi?
aws s3api get-bucket-policy --bucket "$bucket" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo " Bucket Policy: MEVCUT"
else
echo " Bucket Policy: YOK"
fi
# Versioning durumu
VERSIONING=$(aws s3api get-bucket-versioning
--bucket "$bucket"
--query 'Status'
--output text 2>/dev/null)
echo " Versioning: ${VERSIONING:-Disabled}"
echo ""
done
Bu scripti cron’a ekleyip çıktısını bir log bucket’ına veya Slack’e gönderebilirsiniz.
Sık Yapılan Hatalar
“Principal: *” kullanmak ama Block Public Access’i unutmak: Bucket policy’de herkese izin verip Block Public Access’i de açık bırakmak policy’yi etkisiz hale getirir. Kasıtlıysa önce Block Public Access’i kapatmanız gerekir.
Resource ARN’ı yanlış yazmak: s3:ListBucket için arn:aws:s3:::bucket-adi (sondaki / olmadan), object işlemleri için arn:aws:s3:::bucket-adi/ yazılmalı. Bunu karıştırmak “Access Denied” hatalarının en yaygın sebebi.
Condition bloğunda mantık hatası: Birden fazla koşul aynı blokta yazılırsa AND mantığıyla çalışır. OR için ayrı Statement blokları kullanmanız gerekir.
Cross-account’ta object ownership sorununu atlamak: Daha önce bahsedildiği gibi, cross-account upload’larda bucket-owner-full-control ACL koşulunu eklemezseniz bucket sahibi kendi bucket’ındaki dosyalara erişemeyebilir.
Çok geniş Delete izinleri vermek: s3:DeleteObject yerine s3:DeleteObjectVersion ayrı bir izin. Versioning açıksa bu ikisini birbirinden ayırın. Uygulama servisine genellikle sadece belirli bir prefix’teki nesneleri silme izni yeterli.
Policy Simulator ile Test Etmek
Değişiklik yapmadan önce IAM Policy Simulator’ü kullanmak iyi bir alışkanlık:
# Belirli bir rolün S3 bucket'a erişip erişemeyeceğini simüle et
aws iam simulate-principal-policy
--policy-source-arn "arn:aws:iam::123456789012:role/servis-a-rol"
--action-names "s3:GetObject" "s3:PutObject" "s3:DeleteObject"
--resource-arns
"arn:aws:s3:::production-assets/servis-a/dosya.txt"
"arn:aws:s3:::production-assets/servis-b/dosya.txt"
--query 'EvaluationResults[*].{Action:EvalActionName,Resource:EvalResourceName,Decision:EvalDecision}'
--output table
Bu komut size hangi action’ın hangi resource üzerinde allowed/denied olduğunu söyler. Production’da test etmek yerine bunu kullanın.
S3 Access Analyzer
AWS’nin yerleşik aracı olan Access Analyzer, bucket’larınıza dışarıdan (başka hesap, public) erişim olup olmadığını otomatik olarak tespit eder:
# Access Analyzer'ı aktifleştir (bölge bazlı)
aws accessanalyzer create-analyzer
--analyzer-name s3-guvenlik-analizi
--type ACCOUNT
--region eu-west-1
# Mevcut bulguları listele
aws accessanalyzer list-findings
--analyzer-arn "arn:aws:accessanalyzer:eu-west-1:123456789012:analyzer/s3-guvenlik-analizi"
--filter '{"resourceType": {"eq": ["AWS::S3::Bucket"]}}'
--query 'findings[*].{Resource:resource,Status:status,IsPublic:isPublic}'
--output table
Bu aracı düzenli çalıştırıp sonuçları bir ticket sistemine veya güvenlik panosuna entegre etmek, S3 güvenlik posturunuzu sürekli izlemenizi sağlar.
Sonuç
S3 izin modelini doğru anlamak ve uygulamak, AWS’deki en kritik güvenlik konularından biri. Bucket policy, IAM policy ve Block Public Access ayarlarını bir sistem olarak düşünmek gerekiyor. Bunlar birbirinden bağımsız değil, birbirini tamamlayan katmanlar.
Pratikte önerdiğim yaklaşım şu: Her yeni bucket için önce Block Public Access’i tamamen kilitli tutun, sonra gerçekten ihtiyaç duyulan minimum izinleri en az yetkili prensibine göre ekleyin. Bucket policy yazarken mutlaka HTTPS zorunluluğu ve varsa şifreleme koşulunu ekleyin. Access Analyzer’ı aktif tutun ve bulgularını bir izleme sürecine bağlayın.
Güvenlik denetiminizi periyodik yapın. Eski projelerden kalan, artık aktif olmayan roller veya cross-account izinler birikmesi çok kolay. Bir script ile hesabınızdaki tüm bucket’ları tarayıp raporlamak, sizi beklenmedik bir veri sızıntısından koruyabilir.
