Serverless Güvenlik En İyi Uygulamaları

Serverless mimariye geçtiğinizde güvenlik sorumluluklarınız değişiyor, ama asla azalmıyor. Aksine, daha karmaşık bir hal alıyor. Sunucu yönetiminden kurtuluyorsunuz ama fonksiyon izinleri, ortam değişkenleri, API güvenliği ve tedarik zinciri saldırıları gibi yeni tehdit vektörleriyle yüz yüze geliyorsunuz. Bu yazıda AWS Lambda üzerinden ilerleyeceğiz ama prensipler Google Cloud Functions ve Azure Functions için de büyük ölçüde geçerli.

Neden Serverless Güvenlik Farklıdır

Geleneksel bir sunucuda bir şeyleri yanlış yapılandırırsanız, en azından sisteme giriş yapıp durumu inceleyebilirsiniz. Serverless’ta fonksiyonunuz birkaç saniye çalışıp kayboluyor. Log yazmayı unutursanız, ne olduğunu bir daha öğrenemeyebilirsiniz.

Daha da kritik olan şey şu: her fonksiyon, kendi küçük bir uygulama gibi davranıyor. 50 fonksiyonunuz varsa, 50 ayrı güvenlik yüzeyi demek bu. Her birinin ayrı izinleri, bağımlılıkları ve konfigürasyonu var. Bir tane gözden kaçırdığınızda, o gözden kaçan fonksiyon saldırganın giriş kapısı haline gelebiliyor.

Shared Responsibility Model burada kritik bir rol oynuyor. AWS, altta yatan altyapının güvenliğini sağlıyor. Fonksiyon kodunuzun, izinlerinizin, verilerinizin ve konfigürasyonlarınızın güvenliği tamamen sizin sorumluluğunuzda.

Minimum Yetki Prensibini Gerçekten Uygulamak

Sysadmin dünyasında “least privilege” prensibi herkese ezberletilir ama pratikte çoğunlukla göz ardı edilir. Serverless’ta bu prensibi atlamanın bedeli çok daha ağır olabiliyor.

En yaygın hata şu: bir Lambda fonksiyonu oluşturuyorsunuz, aceleyle AdministratorAccess politikasını atıyorsunuz ve “şimdilik böyle kalsın, sonra düzeltirim” diyorsunuz. O “sonra” hiç gelmiyor.

Doğru yaklaşım şöyle görünmeli:

# Önce fonksiyonun hangi kaynaklara erişmesi gerektiğini listeleyin
# Örnek: Sadece belirli bir S3 bucket'a okuma erişimi

aws iam create-policy 
  --policy-name LambdaReadOnlyS3Policy 
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "s3:GetObject",
          "s3:ListBucket"
        ],
        "Resource": [
          "arn:aws:s3:::my-specific-bucket",
          "arn:aws:s3:::my-specific-bucket/*"
        ]
      }
    ]
  }'

Bir fonksiyon sadece bir DynamoDB tablosuna yazıyorsa, ona sadece o tabloya PutItem yetkisi verin. dynamodb:* değil, sadece ihtiyaç duyduğu aksiyonlar.

# Mevcut bir Lambda fonksiyonunun hangi izinlere sahip olduğunu kontrol edin
aws lambda get-function-configuration 
  --function-name my-function 
  --query 'Role'

# Role'un attached politikalarını görüntüleyin
aws iam list-attached-role-policies 
  --role-name my-lambda-execution-role

# Inline politikaları da kontrol etmeyi unutmayın
aws iam list-role-policies 
  --role-name my-lambda-execution-role

Ortam Değişkenlerini Güvenli Tutmak

Ortam değişkenleri serverless’ın en büyük güvenlik tuzaklarından biri. Lambda konsolunda düz metin olarak saklanan database şifreleri, API anahtarları… Bunlar saldırganın ilk bakacağı yerlerden biri.

AWS Secrets Manager veya Parameter Store kullanın. Bu gerçek hayatta nasıl görünüyor:

import boto3
import json
import os

def get_secret(secret_name):
    """
    Secrets Manager'dan secret çeker.
    Fonksiyon soğuk başlangıçta bir kez çalışır,
    sonraki çağrılarda cache'den döner.
    """
    client = boto3.client('secretsmanager')
    
    try:
        response = client.get_secret_value(SecretId=secret_name)
        if 'SecretString' in response:
            return json.loads(response['SecretString'])
    except Exception as e:
        print(f"Secret alınamadı: {e}")
        raise

# Global scope'da bir kez çekilir (warm execution için cache)
_db_credentials = None

def handler(event, context):
    global _db_credentials
    
    if _db_credentials is None:
        _db_credentials = get_secret(os.environ['DB_SECRET_ARN'])
    
    # _db_credentials['username'] ve _db_credentials['password'] kullanılır

Ortam değişkenlerini şifrelemek için de KMS kullanabilirsiniz:

# Lambda fonksiyonunda KMS şifrelemesini etkinleştirin
aws lambda update-function-configuration 
  --function-name my-function 
  --kms-key-arn arn:aws:kms:eu-west-1:123456789012:key/mrk-abc123 
  --environment "Variables={DB_SECRET_ARN=arn:aws:secretsmanager:...}"

# Mevcut ortam değişkenlerini listeleyin (değerleri görmeden)
aws lambda get-function-configuration 
  --function-name my-function 
  --query 'Environment.Variables | keys(@)'

API Gateway Güvenliği

Lambda fonksiyonlarınızı dış dünyaya açan çoğunlukla API Gateway oluyor. Burada yapılan hatalar doğrudan internete açık bir kapı bırakmak anlamına geliyor.

Throttling mutlaka konfigüre edin. Yoksa biri fonksiyonunuzu saniyede binlerce kez çağırır ve hem maliyetiniz patlar hem de uygulamanız çöker.

# API Gateway'de throttling ayarları
aws apigateway update-stage 
  --rest-api-id abc123def 
  --stage-name prod 
  --patch-operations 
    op=replace,path=/defaultRouteSettings/throttlingBurstLimit,value=100 
    op=replace,path=/defaultRouteSettings/throttlingRateLimit,value=50

# Belirli bir endpoint için farklı limit koyabilirsiniz
aws apigateway update-stage 
  --rest-api-id abc123def 
  --stage-name prod 
  --patch-operations 
    op=replace,path=/~1login/POST/throttling/burstLimit,value=10 
    op=replace,path=/~1login/POST/throttling/rateLimit,value=5

WAF entegrasyonunu ihmal etmeyin. API Gateway önüne AWS WAF koyarak SQL injection, XSS ve diğer yaygın saldırıları filtreleyebilirsiniz.

# WAF Web ACL oluşturun ve API Gateway'e bağlayın
aws wafv2 create-web-acl 
  --name "serverless-api-waf" 
  --scope REGIONAL 
  --default-action Allow={} 
  --rules '[
    {
      "Name": "AWSManagedRulesCommonRuleSet",
      "Priority": 1,
      "OverrideAction": {"None": {}},
      "Statement": {
        "ManagedRuleGroupStatement": {
          "VendorName": "AWS",
          "Name": "AWSManagedRulesCommonRuleSet"
        }
      },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "CommonRuleSetMetric"
      }
    }
  ]' 
  --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=ServerlessApiWAF

# WAF'ı API Gateway stage'ine bağlayın
aws wafv2 associate-web-acl 
  --web-acl-arn arn:aws:wafv2:eu-west-1:123456789012:regional/webacl/serverless-api-waf/xxx 
  --resource-arn arn:aws:apigateway:eu-west-1::/restapis/abc123/stages/prod

Bağımlılık Güvenliği ve Tedarik Zinciri Saldırıları

2021’deki Log4Shell olayından sonra herkes bağımlılık güvenliğinin ne kadar kritik olduğunu anladı. Serverless’ta bu konu daha da önemli çünkü deployment paketinize neyin girdiğini tam olarak kontrol etmek zorundasınız.

Gerçek hayat senaryosu: Bir e-ticaret şirketi, ödeme işleme Lambda fonksiyonunda kullandıkları npm paketlerinden birinin, başka bir paketin sahte versiyonunu içerdiğini ancak bir güvenlik audit’inde fark etti. O sahte paket, işlem verilerini dış bir sunucuya gönderiyordu.

Bu tür durumları önlemek için:

# Node.js projelerinde güvenlik açıklarını tarayın
npm audit

# Kritik açıkları otomatik düzeltin
npm audit fix

# Python projelerinde safety kullanın
pip install safety
safety check -r requirements.txt

# Bağımlılıkları lock file ile sabitleyin
# Node.js
npm ci  # package-lock.json'dan tam sürümleri kullanır

# Python
pip freeze > requirements.txt
# veya pip-compile kullanın
pip-compile --generate-hashes requirements.in

Dependency scanning’i CI/CD pipeline’ınıza entegre edin:

#!/bin/bash
# deploy_check.sh - Deployment öncesi çalışan güvenlik kontrol scripti

echo "Bağımlılık güvenliği taranıyor..."

# Python bağımlılıklarını kontrol et
if [ -f "requirements.txt" ]; then
    pip install safety --quiet
    safety check -r requirements.txt
    if [ $? -ne 0 ]; then
        echo "HATA: Kritik güvenlik açıkları bulundu. Deployment durduruldu."
        exit 1
    fi
fi

# Node.js bağımlılıklarını kontrol et
if [ -f "package.json" ]; then
    npm audit --audit-level=high
    if [ $? -ne 0 ]; then
        echo "HATA: Yüksek seviyeli güvenlik açıkları bulundu. Deployment durduruldu."
        exit 1
    fi
fi

echo "Güvenlik taraması başarıyla tamamlandı."

VPC Konfigürasyonu ve Ağ Güvenliği

Lambda fonksiyonlarını varsayılan olarak public internet üzerinden çalışır. Eğer fonksiyonunuzun RDS, ElastiCache veya diğer özel kaynaklara erişmesi gerekiyorsa, VPC içinde çalıştırmanız şart.

# Lambda fonksiyonunu VPC'ye bağlayın
aws lambda update-function-configuration 
  --function-name my-db-function 
  --vpc-config SubnetIds=subnet-private-1a,subnet-private-1b,SecurityGroupIds=sg-lambda-outbound

# VPC'deki Lambda için gerekli Security Group kuralları
# Outbound: Sadece RDS security group'una belirli port üzerinden
aws ec2 authorize-security-group-egress 
  --group-id sg-lambda-outbound 
  --protocol tcp 
  --port 5432 
  --destination-group sg-rds-access

# Lambda'nın internete çıkması gerekmiyorsa
# outbound kurallarını kısıtlayın
# Secrets Manager, S3 gibi AWS servisleri için VPC Endpoint kullanın
aws ec2 create-vpc-endpoint 
  --vpc-id vpc-12345678 
  --service-name com.amazonaws.eu-west-1.secretsmanager 
  --vpc-endpoint-type Interface 
  --subnet-ids subnet-private-1a subnet-private-1b 
  --security-group-ids sg-vpc-endpoints

Önemli not: Lambda’yı VPC’ye koyduğunuzda cold start süreleri artabilir. Bunu ENI (Elastic Network Interface) provisioning gecikmesi oluşturur. AWS son yıllarda bunu büyük ölçüde iyileştirdi ama yine de göz önünde bulundurmanız gereken bir trade-off.

Fonksiyon Timeout ve Concurrency Sınırları

Güvenliğin genellikle göz ardı edilen bir boyutu bu. Sınırsız concurrency, hem maliyeti patlatan hem de potansiyel olarak DoS saldırısı için kullanılabilecek bir açık bırakır.

# Rezerve concurrency limiti koyun
aws lambda put-function-concurrency 
  --function-name payment-processor 
  --reserved-concurrent-executions 50

# Provisioned concurrency için de limit belirleyin
aws lambda put-provisioned-concurrency-config 
  --function-name payment-processor 
  --qualifier prod 
  --provisioned-concurrent-executions 10

# Fonksiyon timeout'unu makul bir değere ayarlayın
# Varsayılan 3 saniye genellikle çok az, ama 15 dakika da saçmalık
aws lambda update-function-configuration 
  --function-name my-api-function 
  --timeout 30  # 30 saniye çoğu API fonksiyonu için yeterli

# Concurrency kullanımını izleyin
aws cloudwatch get-metric-statistics 
  --namespace AWS/Lambda 
  --metric-name ConcurrentExecutions 
  --dimensions Name=FunctionName,Value=payment-processor 
  --start-time 2024-01-01T00:00:00Z 
  --end-time 2024-01-02T00:00:00Z 
  --period 3600 
  --statistics Maximum

Logging, Monitoring ve Incident Response

Serverless’ta olay müdahalesinin en zor kısmı, forensic analiz yapmak. Sunucu yok, disk yok, process listesi yok. Her şey loglardan ibaret.

Structured logging kullanın. Düz metin log yerine JSON formatında log yazın, CloudWatch Insights ile kolayca sorgulayın.

import json
import logging
import os
from datetime import datetime

# Logger konfigürasyonu
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def log_event(level, message, **kwargs):
    """Structured log yazar."""
    log_entry = {
        "timestamp": datetime.utcnow().isoformat(),
        "level": level,
        "message": message,
        "function_name": os.environ.get("AWS_LAMBDA_FUNCTION_NAME"),
        "function_version": os.environ.get("AWS_LAMBDA_FUNCTION_VERSION"),
        **kwargs
    }
    
    if level == "ERROR":
        logger.error(json.dumps(log_entry))
    elif level == "WARNING":
        logger.warning(json.dumps(log_entry))
    else:
        logger.info(json.dumps(log_entry))

def handler(event, context):
    # Her API çağrısını logla ama hassas veriyi loglama
    log_event("INFO", "Fonksiyon başlatıldı",
        request_id=context.aws_request_id,
        event_source=event.get("source", "unknown"),
        # Asla şifre, token, kredi kartı numarası loglama
    )

CloudWatch Insights ile şüpheli aktiviteleri sorgulamak:

# Son 1 saatte hata veren fonksiyon çağrılarını bul
aws logs start-query 
  --log-group-name /aws/lambda/my-function 
  --start-time $(date -d '1 hour ago' +%s) 
  --end-time $(date +%s) 
  --query-string '
    fields @timestamp, @message
    | filter level = "ERROR"
    | sort @timestamp desc
    | limit 50
  '

# Yüksek hata oranlarını tespit et
aws cloudwatch put-metric-alarm 
  --alarm-name "LambdaHighErrorRate" 
  --alarm-description "Lambda fonksiyonu yüksek hata oranı" 
  --metric-name Errors 
  --namespace AWS/Lambda 
  --dimensions Name=FunctionName,Value=payment-processor 
  --statistic Sum 
  --period 300 
  --threshold 10 
  --comparison-operator GreaterThanThreshold 
  --evaluation-periods 2 
  --alarm-actions arn:aws:sns:eu-west-1:123456789012:ops-alerts

Kod Güvenliği ve Input Validasyonu

Lambda fonksiyonlarınız dışarıdan veri aldığında, o veriye asla güvenmeyin. Event injection saldırıları serverless’ta özellikle tehlikeli.

import re
import json

def validate_input(event):
    """Event verilerini doğrular ve sanitize eder."""
    
    # Body varsa parse et
    body = {}
    if event.get('body'):
        try:
            body = json.loads(event['body'])
        except json.JSONDecodeError:
            raise ValueError("Geçersiz JSON formatı")
    
    # Gerekli alanları kontrol et
    required_fields = ['user_id', 'action']
    for field in required_fields:
        if field not in body:
            raise ValueError(f"Eksik alan: {field}")
    
    # user_id formatını doğrula (sadece alphanumeric ve tire)
    if not re.match(r'^[a-zA-Z0-9-]{1,50}$', body['user_id']):
        raise ValueError("Geçersiz user_id formatı")
    
    # İzin verilen aksiyonları kontrol et
    allowed_actions = ['read', 'update', 'delete']
    if body['action'] not in allowed_actions:
        raise ValueError(f"İzinsiz aksiyon: {body['action']}")
    
    return body

def handler(event, context):
    try:
        validated_data = validate_input(event)
        # İş mantığına devam et
    except ValueError as e:
        return {
            'statusCode': 400,
            'body': json.dumps({'error': str(e)})
        }

Infrastructure as Code ile Güvenliği Kodlamak

Güvenlik konfigürasyonlarını elle yapmak hem hata eğilimli hem de tekrarlanamaz. Terraform veya AWS SAM kullanarak güvenlik politikalarınızı kod olarak tanımlayın.

# Terraform ile güvenli Lambda konfigürasyonu - temel yapı
cat > lambda_security.tf << 'EOF'
resource "aws_iam_role" "lambda_role" {
  name = "secure-lambda-role"
  
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "lambda.amazonaws.com"
      }
    }]
  })
  
  # Permission boundary ekle - izinlerin yanlışlıkla 
  # genişletilmesini engeller
  permissions_boundary = aws_iam_policy.lambda_boundary.arn
}

resource "aws_lambda_function" "secure_function" {
  function_name = "secure-api-handler"
  role          = aws_iam_role.lambda_role.arn
  
  # Timeout makul bir değerde
  timeout = 30
  
  # Concurrency sınırı
  reserved_concurrent_executions = 100
  
  # KMS ile ortam değişkeni şifrelemesi
  kms_key_arn = aws_kms_key.lambda_key.arn
  
  # VPC konfigürasyonu
  vpc_config {
    subnet_ids         = var.private_subnet_ids
    security_group_ids = [aws_security_group.lambda_sg.id]
  }
  
  # X-Ray tracing
  tracing_config {
    mode = "Active"
  }
  
  environment {
    variables = {
      DB_SECRET_ARN = aws_secretsmanager_secret.db_secret.arn
      ENVIRONMENT   = "production"
    }
  }
}
EOF

echo "Terraform plan çalıştırılıyor..."
terraform plan -out=tfplan
terraform show -json tfplan | jq '.resource_changes[] | select(.type == "aws_lambda_function")'

Güvenlik Audit ve Compliance Kontrolleri

Düzenli güvenlik denetimi yapmadan, zamanla konfigürasyonlar kayıyor ve güvenlik açıkları birikmeye başlıyor. AWS Config ve Security Hub bunu otomatize etmenizi sağlıyor.

# AWS Config ile Lambda güvenlik kurallarını etkinleştirin
aws configservice put-config-rule 
  --config-rule '{
    "ConfigRuleName": "lambda-function-public-access-prohibited",
    "Source": {
      "Owner": "AWS",
      "SourceIdentifier": "LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED"
    }
  }'

# Tüm Lambda fonksiyonlarını listele ve VPC kontrolü yap
aws lambda list-functions 
  --query 'Functions[*].[FunctionName, VpcConfig.VpcId]' 
  --output table

# VPC'siz fonksiyonları bul
aws lambda list-functions 
  --query 'Functions[?VpcConfig.VpcId == null].FunctionName' 
  --output json

# Environment variable'larda şüpheli içerik var mı diye bak
# (KMS şifrelemesi yoksa dikkatli olun)
aws lambda list-functions 
  --query 'Functions[?Environment != null].[FunctionName]' 
  --output text | while read func; do
    echo "Kontrol ediliyor: $func"
    aws lambda get-function-configuration 
      --function-name "$func" 
      --query 'KMSKeyArn'
done

Sonuç

Serverless güvenlik, “kurup unut” anlayışıyla çalışmıyor. Aksine, geleneksel sunucu güvenliğinden daha fazla dikkat ve otomasyona ihtiyaç duyuyor.

Özetlemek gerekirse en kritik noktalar şunlar:

  • Minimum yetki prensibini taviz vermeden uygulayın. Her fonksiyona tam ihtiyaç duyduğu izinleri verin, bir fazlasını değil.
  • Secrets Manager’ı kullanın. Ortam değişkenlerinde düz metin credentials kesinlikle kabul edilemez.
  • API Gateway’i koruyun. Throttling, WAF ve authentication olmadan bir fonksiyon internete doğrudan açık bir kapıdır.
  • Bağımlılıklarınızı takip edin. CI/CD pipeline’ınıza güvenlik taramasını mutlaka entegre edin.
  • Her şeyi loglayin, ama hassas veriyi değil. Olayları forensic analiz yapabilmek için structured logging şart.
  • Infrastructure as Code kullanın. Güvenlik konfigürasyonu elle yapılmamalı, kod olarak tanımlanmalı ve review edilmeli.
  • Düzenli audit yapın. Zamanla konfigürasyonlar kayar. Aylık veya en azından çeyreklik güvenlik gözden geçirmesi yapın.

Serverless’ın sunduğu kolaylıklar gerçek, ama bu kolaylıklar güvenliği arka plana itmenin bahanesi olamaz. İyi konfigüre edilmiş bir serverless uygulama, geleneksel sunuculara kıyasla çok daha güvenli olabilir. Kötü konfigüre edilmişi ise tam tersi.

Bir yanıt yazın

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