AWS Secrets Manager ile Gizli Veri Yönetimi

Üretim ortamında bir gün gelir, ekipten biri veritabanı şifresini direkt olarak uygulama koduna gömmüş olduğunu fark edersiniz. Ya da daha kötüsü, bir CI/CD pipeline’ında API anahtarını plain-text olarak görürsünüz. Bu tür durumlar, özellikle büyüyen ekiplerde kaçınılmaz hale gelir. AWS Secrets Manager tam da bu noktada devreye giriyor ve gizli veri yönetimini merkezi, güvenli ve otomatik hale getiriyor.

Bu yazıda AWS Secrets Manager’ı sıfırdan kurarak gerçek dünya senaryolarında nasıl kullanacağınızı, uygulama entegrasyonunu ve en iyi pratikleri ele alacağız.

AWS Secrets Manager Nedir ve Neden Önemlidir

AWS Secrets Manager, veritabanı kimlik bilgileri, API anahtarları, OAuth token’ları ve diğer gizli verileri güvenli bir şekilde saklamanızı, yönetmenizi ve otomatik olarak rotasyona tabi tutmanızı sağlayan tam yönetilen bir servistir.

Klasik yaklaşımda ne yapılır? Şifreler .env dosyalarına yazılır, bu dosyalar git’e commit edilir (bazen yanlışlıkla, bazen kasıtlı olarak), sonra bir gün bu repo public olur veya bir çalışan ayrılır. Secrets Manager bu zinciri tamamen kırar.

Temel avantajları şunlardır:

  • Merkezi yönetim: Tüm gizli veriler tek bir yerde, IAM ile korunan bir vault’ta
  • Otomatik rotasyon: RDS, Redshift ve diğer servisler için Lambda tabanlı otomatik şifre yenileme
  • Denetim izi: CloudTrail ile her erişim loglanır, kim ne zaman hangi secret’a erişti
  • Fine-grained access control: IAM policy’leri ile hangi uygulama hangi secret’ı okuyabilir
  • Çapraz bölge replikasyon: Multi-region mimarilerde tutarlılık
  • KMS entegrasyonu: Secret’lar AWS KMS ile şifrelenir

Parameter Store ile sık sık karıştırılır. Temel fark şudur: Parameter Store ücretsiz bir tier’a sahip, ancak otomatik rotasyon ve daha gelişmiş secret yönetimi özellikleri Secrets Manager’da bulunur. Eğer kritik kimlik bilgileri yönetiyorsanız Secrets Manager’ın maliyeti çok daha anlamlıdır.

İlk Secret’ı Oluşturma

AWS CLI üzerinden ilk secret’ınızı oluşturalım. Önce AWS CLI’nin kurulu ve yapılandırılmış olduğundan emin olun.

# Basit bir key-value secret oluşturma
aws secretsmanager create-secret 
  --name "prod/myapp/database" 
  --description "Production veritabani kimlik bilgileri" 
  --secret-string '{"username":"dbadmin","password":"SuperSecure123!","host":"prod-db.cluster.eu-west-1.rds.amazonaws.com","port":"5432","dbname":"myapp_prod"}'

# Oluşturulan secret'ı doğrulama
aws secretsmanager describe-secret 
  --secret-id "prod/myapp/database"

Secret isimlerinde bir isimlendirme standardı belirlemek çok önemlidir. Ben genellikle {ortam}/{uygulama}/{servis} formatını kullanıyorum. Bu sayede IAM policy’lerinde wildcard kullanmak çok kolaylaşır.

Mevcut bir secret’ı güncellemek için:

# Secret değerini güncelleme
aws secretsmanager put-secret-value 
  --secret-id "prod/myapp/database" 
  --secret-string '{"username":"dbadmin","password":"YeniSifre456!","host":"prod-db.cluster.eu-west-1.rds.amazonaws.com","port":"5432","dbname":"myapp_prod"}'

# Secret değerini okuma
aws secretsmanager get-secret-value 
  --secret-id "prod/myapp/database" 
  --query 'SecretString' 
  --output text

IAM Policy Yapılandırması

Secrets Manager’ın gücü, granüler erişim kontrolünden geliyor. Her uygulamanın sadece kendi ihtiyacı olan secret’lara erişmesi gerekir. Bu “least privilege” prensibinin ta kendisidir.

Bir uygulama için IAM policy örneği:

# IAM policy dosyası oluşturma
cat > myapp-secrets-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAppSecretsRead",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": [
        "arn:aws:secretsmanager:eu-west-1:123456789012:secret:prod/myapp/*"
      ]
    },
    {
      "Sid": "AllowKMSDecrypt",
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt",
        "kms:DescribeKey"
      ],
      "Resource": "arn:aws:kms:eu-west-1:123456789012:key/mrk-1234abcd5678efgh"
    }
  ]
}
EOF

# Policy'yi oluştur ve role ekle
aws iam create-policy 
  --policy-name "MyAppSecretsReadPolicy" 
  --policy-document file://myapp-secrets-policy.json

# EC2 instance role'una policy'yi ekle
aws iam attach-role-policy 
  --role-name "MyAppInstanceRole" 
  --policy-arn "arn:aws:iam::123456789012:policy/MyAppSecretsReadPolicy"

Resource ARN’de wildcard kullanımı dikkat ister. prod/myapp/* şeklinde bir pattern, uygulamanın sadece kendi prefix’iyle başlayan secret’lara erişmesini sağlar. Farklı ortamlar (dev, staging, prod) için farklı roller ve policy’ler oluşturmak en temiz yaklaşımdır.

Python ile Uygulama Entegrasyonu

Gerçek dünyada en çok Python veya Node.js uygulamalarıyla entegrasyon yapılır. Bir Django uygulaması için Secrets Manager entegrasyonu nasıl yapılır, birlikte görelim.

#!/usr/bin/env python3
# secrets_helper.py - Uygulamanizda kullanabileceginiz yardimci modul

import boto3
import json
import logging
from botocore.exceptions import ClientError
from functools import lru_cache

logger = logging.getLogger(__name__)

class SecretsManager:
    def __init__(self, region_name='eu-west-1'):
        self.client = boto3.client(
            service_name='secretsmanager',
            region_name=region_name
        )
    
    @lru_cache(maxsize=None)
    def get_secret(self, secret_name):
        """
        Secret degerini cache'leyerek getirir.
        Lambda veya kisa yasayan processlerde cok faydalidir.
        """
        try:
            response = self.client.get_secret_value(
                SecretId=secret_name
            )
        except ClientError as e:
            error_code = e.response['Error']['Code']
            
            if error_code == 'DecryptionFailureException':
                logger.error(f"KMS ile sifre cozulemedi: {secret_name}")
                raise
            elif error_code == 'InternalServiceErrorException':
                logger.error("Secrets Manager dahili hatasi")
                raise
            elif error_code == 'InvalidParameterException':
                logger.error(f"Gecersiz parametre: {secret_name}")
                raise
            elif error_code == 'InvalidRequestException':
                logger.error(f"Gecersiz istek: {secret_name}")
                raise
            elif error_code == 'ResourceNotFoundException':
                logger.error(f"Secret bulunamadi: {secret_name}")
                raise
            else:
                raise
        
        if 'SecretString' in response:
            return json.loads(response['SecretString'])
        else:
            # Binary secret durumu
            import base64
            return base64.b64decode(response['SecretBinary'])
    
    def get_db_config(self, secret_name):
        """Veritabani yapilandirmasini Django uyumlu formatta donderir."""
        secret = self.get_secret(secret_name)
        return {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': secret['dbname'],
            'USER': secret['username'],
            'PASSWORD': secret['password'],
            'HOST': secret['host'],
            'PORT': secret['port'],
        }


# Django settings.py icinde kullanim
secrets = SecretsManager(region_name='eu-west-1')

DATABASES = {
    'default': secrets.get_db_config('prod/myapp/database')
}

# API anahtari kullanimi
stripe_config = secrets.get_secret('prod/myapp/stripe')
STRIPE_SECRET_KEY = stripe_config['secret_key']
STRIPE_PUBLISHABLE_KEY = stripe_config['publishable_key']

lru_cache kullanımı kritik bir detay. Lambda fonksiyonları veya container’lar her başlatıldığında Secrets Manager’a istek atmak yerine, aynı process ömrü boyunca cache’den servis eder. Bu hem latency’yi düşürür hem de API çağrı maliyetini azaltır. Tabi ki rotasyon durumlarında cache’i temizlemeniz gerekebilir.

Otomatik Rotasyon Yapılandırması

Secrets Manager’ın en güçlü özelliklerinden biri otomatik rotasyon. RDS için AWS’in hazır Lambda fonksiyonlarını kullanabilirsiniz.

# RDS için otomatik rotasyonu aktif etme
# Once Secrets Manager'in VPC'nize erismesi icin endpoint olusturun (önerilir)
aws ec2 create-vpc-endpoint 
  --vpc-id vpc-0abc123def456789 
  --service-name com.amazonaws.eu-west-1.secretsmanager 
  --vpc-endpoint-type Interface 
  --subnet-ids subnet-0abc123 subnet-0def456 
  --security-group-ids sg-0abc123def

# RDS single-user rotasyon Lambda'sini deploy et
aws serverlessrepo create-cloud-formation-change-set 
  --application-id arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSPostgreSQLRotationSingleUser 
  --stack-name SecretsManagerRotationRDS 
  --parameter-overrides '[{"Name":"endpoint","Value":"https://secretsmanager.eu-west-1.amazonaws.com"},{"Name":"functionName","Value":"SecretsManagerRDSRotation"}]' 
  --capabilities CAPABILITY_IAM CAPABILITY_RESOURCE_POLICY

# Rotasyonu aktif et - 30 günde bir otomatik rotasyon
aws secretsmanager rotate-secret 
  --secret-id "prod/myapp/database" 
  --rotation-lambda-arn "arn:aws:lambda:eu-west-1:123456789012:function:SecretsManagerRDSRotation" 
  --rotation-rules AutomaticallyAfterDays=30

Rotasyon aktif olduktan sonra Secrets Manager arka planda şu adımları izler: yeni kimlik bilgisini oluşturur, veritabanında günceller, test eder ve başarılıysa eski versiyonu “previous” olarak işaretler. Uygulamanız her zaman AWSCURRENT versiyonunu çeker, geçiş süresince AWSPENDING versiyonu da geçerlidir.

AWS Lambda’da Secrets Manager Kullanımı

Lambda fonksiyonlarında secret yönetimi biraz farklı bir yaklaşım gerektirir. Lambda Extension kullanımı burada gerçekten işinizi kolaylaştırıyor.

# lambda_function.py
import boto3
import json
import os
from botocore.exceptions import ClientError

# Global scope'ta tanimla - cold start'ta bir kere calisir
secrets_client = boto3.client('secretsmanager', region_name=os.environ.get('AWS_REGION', 'eu-west-1'))
_secrets_cache = {}

def get_cached_secret(secret_name, force_refresh=False):
    """
    Lambda execution context boyunca secret'i cache'ler.
    Warm invocation'larda API cagrisi yapmaz.
    """
    if secret_name not in _secrets_cache or force_refresh:
        try:
            response = secrets_client.get_secret_value(SecretId=secret_name)
            _secrets_cache[secret_name] = json.loads(response['SecretString'])
        except ClientError as e:
            print(f"Secret alinamadi: {e}")
            raise
    
    return _secrets_cache[secret_name]

def lambda_handler(event, context):
    # Secret'i once env variable'dan al, yoksa direkt isim kullan
    secret_name = os.environ.get('DB_SECRET_NAME', 'prod/myapp/database')
    
    try:
        db_config = get_cached_secret(secret_name)
        
        # Veritabani baglantisi kur ve islemi yap
        connection_string = (
            f"postgresql://{db_config['username']}:{db_config['password']}"
            f"@{db_config['host']}:{db_config['port']}/{db_config['dbname']}"
        )
        
        # ... uygulama mantigi
        
        return {
            'statusCode': 200,
            'body': json.dumps({'status': 'success'})
        }
    
    except Exception as e:
        print(f"Hata: {e}")
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

Lambda’da önemli bir nokta: secret adını direkt koda yazmak yerine environment variable olarak geçirin. Bu sayede farklı ortamlar için aynı Lambda kodunu kullanabilirsiniz, sadece environment variable değişir.

Terraform ile Infrastructure as Code

Production ortamlarında Secrets Manager kaynaklarını Terraform ile yönetmek en temiz yaklaşımdır.

# secrets.tf - Terraform ile Secrets Manager yapılandırması

# Temel secret kaynagi
resource "aws_secretsmanager_secret" "app_db" {
  name        = "prod/myapp/database"
  description = "Production veritabani kimlik bilgileri"
  
  # KMS key ile sifreleme
  kms_key_id = aws_kms_key.secrets.arn
  
  # Silme korumasi - kazara silmeye karsi
  recovery_window_in_days = 7
  
  # Replica - DR bölgesi için
  replica {
    region = "eu-central-1"
  }
  
  tags = {
    Environment = "production"
    Application = "myapp"
    ManagedBy   = "terraform"
  }
}

# Secret degerini ayri kaynak olarak tut
# NOT: terraform state dosyasi sifrelenmeli!
resource "aws_secretsmanager_secret_version" "app_db" {
  secret_id = aws_secretsmanager_secret.app_db.id
  
  secret_string = jsonencode({
    username = var.db_username
    password = var.db_password
    host     = aws_db_instance.main.endpoint
    port     = "5432"
    dbname   = var.db_name
  })
  
  lifecycle {
    ignore_changes = [secret_string]
  }
}

# Rotasyon yapilandirmasi
resource "aws_secretsmanager_secret_rotation" "app_db" {
  secret_id           = aws_secretsmanager_secret.app_db.id
  rotation_lambda_arn = aws_lambda_function.rotation.arn
  
  rotation_rules {
    automatically_after_days = 30
  }
}

# KMS key - secret'lar icin ozel key
resource "aws_kms_key" "secrets" {
  description             = "Secrets Manager icin KMS anahtari"
  deletion_window_in_days = 10
  enable_key_rotation     = true
  
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "Enable IAM User Permissions"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }
        Action   = "kms:*"
        Resource = "*"
      }
    ]
  })
}

Terraform ile secret değerlerini yönetirken dikkat etmeniz gereken en önemli şey: terraform.tfstate dosyasında secret değerleri plain-text olarak saklanır. Bu yüzden state dosyasını S3’te şifreli olarak saklamalı ve state’e erişimi kısıtlamalısınız.

CI/CD Pipeline Entegrasyonu

GitHub Actions veya AWS CodePipeline’da Secrets Manager kullanımı biraz farklı çalışır. Pipeline’ın çalıştığı ortamın gerekli IAM yetkilerine sahip olması gerekir.

#!/bin/bash
# deploy.sh - CI/CD pipeline deploy scripti

set -euo pipefail

ENVIRONMENT=${1:-staging}
SECRET_PREFIX="${ENVIRONMENT}/myapp"

echo "Secret'lar yukleniyor: ${SECRET_PREFIX}"

# Tum uygulama secret'larini cek ve export et
get_secret_value() {
    local secret_name=$1
    local key=$2
    
    aws secretsmanager get-secret-value 
        --secret-id "${secret_name}" 
        --query 'SecretString' 
        --output text | jq -r ".${key}"
}

# Veritabani secret'larini al
DB_SECRET="${SECRET_PREFIX}/database"
export DB_HOST=$(get_secret_value ${DB_SECRET} "host")
export DB_USER=$(get_secret_value ${DB_SECRET} "username")
export DB_PASS=$(get_secret_value ${DB_SECRET} "password")
export DB_NAME=$(get_secret_value ${DB_SECRET} "dbname")

# Application secret'larini al
APP_SECRET="${SECRET_PREFIX}/application"
export SECRET_KEY=$(get_secret_value ${APP_SECRET} "django_secret_key")
export REDIS_URL=$(get_secret_value ${APP_SECRET} "redis_url")

echo "Secret'lar basariyla yuklendi"
echo "Deploy basliyor: ${ENVIRONMENT}"

# Docker build ve push
docker build 
    --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') 
    -t myapp:${GITHUB_SHA:-latest} .

# ECS service guncelle
aws ecs update-service 
    --cluster "myapp-${ENVIRONMENT}" 
    --service "myapp-web" 
    --force-new-deployment

echo "Deploy tamamlandi"

# Guvenlik: Export edilen degerleri temizle
unset DB_HOST DB_USER DB_PASS DB_NAME SECRET_KEY REDIS_URL

Bu yaklaşımda script bittiğinde export edilen değerler temizlenir. Ancak process listesi veya memory dump gibi vektörler için daha paranoid bir yaklaşım istiyorsanız, değerleri hiç export etmeyip direkt container’a secret ARN’lerini geçirip uygulama içinde çözmeniz daha güvenlidir.

Monitoring ve Denetim

CloudTrail ile Secrets Manager aktivitelerini izlemek, güvenlik ekibinin hayatını kolaylaştırır.

# Son 24 saatteki tum secret erisimlerini listele
aws cloudtrail lookup-events 
  --lookup-attributes AttributeKey=EventSource,AttributeValue=secretsmanager.amazonaws.com 
  --start-time $(date -u -d '24 hours ago' '+%Y-%m-%dT%H:%M:%S') 
  --query 'Events[].{Time:EventTime,User:Username,Action:EventName,Secret:Resources[0].ResourceName}' 
  --output table

# Belirli bir secret'a yapilan erisimler
aws cloudtrail lookup-events 
  --lookup-attributes AttributeKey=ResourceName,AttributeValue="prod/myapp/database" 
  --query 'Events[].{Time:EventTime,User:Username,Action:EventName}' 
  --output json

# CloudWatch Alarm - beklenmeyen secret erisimi icin
aws cloudwatch put-metric-alarm 
  --alarm-name "UnexpectedSecretsAccess" 
  --alarm-description "Beklenmeyen secret erisimi tespit edildi" 
  --metric-name "CallCount" 
  --namespace "AWS/SecretsManager" 
  --statistic Sum 
  --period 300 
  --threshold 50 
  --comparison-operator GreaterThanThreshold 
  --evaluation-periods 1 
  --alarm-actions "arn:aws:sns:eu-west-1:123456789012:SecurityAlerts"

Monitoring kurulumu sıklıkla ertelenen ama sonradan çok pişman olunan bir adımdır. En azından şu iki şeyi mutlaka yapın: CloudTrail’i aktif edin ve beklenmeyen hacimde secret erişimi için bir alarm kurun.

Maliyet Optimizasyonu

Secrets Manager’ın maliyeti secret başına aylık 0,40 dolar ve 10.000 API çağrısı başına 0,05 dolardır. Büyük ölçekte bu rakamlar birikebilir.

Maliyeti düşürmek için uygulayabileceğiniz stratejiler:

  • Agresif caching: Uygulama başlangıcında secret’ı bir kere çekip memory’de tutun. Rotasyon durumlarında uygulamayı yeniden başlatmak veya rotation event’ini dinlemek daha ekonomiktir.
  • Batch okuma: Birden fazla secret varsa, ilgili değerleri tek bir JSON secret içinde gruplayın.
  • Parameter Store kombinasyonu: Hassas olmayan konfigürasyon değerleri için (feature flag, log level vb.) ücretsiz olan Parameter Store’u kullanın, sadece gerçek secret’lar için Secrets Manager’ı tercih edin.
  • VPC Endpoint: VPC endpoint kullanımı API çağrılarını daha hızlı yapar ve NAT Gateway maliyetini azaltır.

Sık Yapılan Hatalar

Yıllar içinde gördüğüm en yaygın hatalar bunlardır:

  • Secret değerini log’a basmak: Uygulamanızda debug logging açıksa, secret değeri CloudWatch Logs’a düşebilir. Her zaman secret değerini loglarken maskeleme yapın.
  • Çok geniş IAM policy: secretsmanager:* yetkisi vermek, “şimdilik hızlıca çalışsın” mantığıyla başlar ve production’da güvenlik açığı olarak kalır.
  • Terraform state güvensizliği: State dosyasını S3’te encryption ve versioning olmadan saklamak, secret’ları açıkta bırakmak anlamına gelir.
  • Rotasyon sonrası test yapmamak: Rotasyon aktif ettikten sonra mutlaka bir test rotasyonu çalıştırın ve uygulamanın yeni kimlik bilgileriyle çalıştığını doğrulayın.
  • Çok fazla granülarite: Her uygulama bileşeni için onlarca ayrı secret oluşturmak yönetimi zorlaştırır. Mantıksal gruplar halinde secret’ları organize edin.

Sonuç

AWS Secrets Manager, modern cloud native uygulama geliştirmenin vazgeçilmez bir parçasıdır. Kurulum maliyeti ve öğrenme eğrisi düşük, ancak sağladığı güvenlik ve operasyonel kolaylık ciddi boyuttadır.

En kritik adımlar şunlardır: önce isimlendirme standardını belirleyin, IAM policy’leri least privilege prensibine göre yazın, otomatik rotasyonu aktif edin ve CloudTrail monitoring kurun. Bu dört adımı doğru yaparsanız, gizli veri yönetimi açısından sağlam bir temele oturmuş olursunuz.

Kodunuzda tek bir hard-coded şifre bile kalmaması için ekibinize bu konuyu anlatın, code review süreçlerinize secret tespiti için tooling ekleyin (git-secrets veya truffleHog gibi araçlar). Teknik çözüm kadar, ekip kültürü de bu konuda belirleyicidir.

Bir yanıt yazın

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