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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir