2025-12-23
AWS Secrets Manager & Parameter Store: Güvenlik Best Practices
AWS Secrets Manager ve Systems Manager Parameter Store'u karşılaştıran kapsamlı teknik rehber - hangi servisi ne zaman kullanmalı ve gerçek dünya implementation pattern'leri.
AWS’de çalışan mühendisler sık sık aynı dilemmayla karşılaşır: secrets management için Secrets Manager mi yoksa Parameter Store mu kullanmalı? Her iki servis de sensitive data saklıyor ama farklı amaçlara hizmet ediyorlar ve farklı maliyet yapıları var. Bu rehber teknik karar kriterleri, complete implementation pattern’leri ve gerçek dünyadan öğrenilmiş dersleri içeriyor. Kısa kural: statik config → Parameter Store; RDS şifreleri ve rotation → Secrets Manager.
Servisleri Anlamak
Implementation’a geçmeden önce, bu servisler arasındaki teknik farkları ortaya koyalım. Cross-account secret paylaşımı gerekiyorsa Secrets Manager resource policy’leri daha esnek; Parameter Store için RAM (Resource Access Manager) kullanırsınız.
Servis Karşılaştırması
| Feature | Parameter Store Standard | Parameter Store Advanced | Secrets Manager |
|---|---|---|---|
| Maliyet | Ücretsiz | $0.05/secret/ay | $0.40/secret/ay |
| Max Boyut | 4 KB | 8 KB | 64 KB |
| Rotation | Sadece manuel | Sadece manuel | Lambda ile otomatik |
| Versioning | Tek aktif version | Tek aktif version | Multiple concurrent version’lar |
| Cross-Account | RAM ile (2024’ten beri) | RAM ile (2024’ten beri) | Native resource policy’ler |
| Multi-Region | Manuel replication | Manuel replication | Otomatik replication |
| Encryption | Opsiyonel (SecureString) | Opsiyonel (SecureString) | Her zaman encrypted (zorunlu) |
| Native Integration’lar | Basit | Basit | RDS, Redshift, DocumentDB |
Önemli Teknik İçgörü: Parameter Store SecureString ile KMS encryption kullanır ve ücretsiz temel secrets management sağlar. Secrets Manager buna otomatik rotation, native RDS entegrasyonu ve staging label’ları ile built-in versioning ekler.
Karar Framework’ü
Doğru servisi seçmek için bu teknik decision tree’yi kullan:
Maliyet Analizi Örneği:
- 10 statik API key → Parameter Store Standard: $0/ay
- 5 RDS password rotation ile → Secrets Manager: $2.00/ay
- 20 configuration value → Parameter Store Standard: $0/ay
- Toplam: 10.00/ay
Cross-Account Secret Sharing
En yaygın requirement’lardan biri AWS account’ları arasında secret paylaşımı. İşte complete implementation pattern:
Architecture Genel Bakış
Implementation - Account A (Secret Owner)
// CDK code for Account A
import * as cdk from 'aws-cdk-lib';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as iam from 'aws-cdk-lib/aws-iam';
const kmsKey = new kms.Key(this, 'SecretKey', {
enableKeyRotation: true,
description: 'KMS key for cross-account secret sharing',
});
// Account B'ye key kullanma izni ver
kmsKey.addToResourcePolicy(new iam.PolicyStatement({
sid: 'AllowAccountBDecrypt',
principals: [new iam.AccountPrincipal('222222222222')], // Account B
actions: ['kms:Decrypt', 'kms:DescribeKey'],
resources: ['*'],
conditions: {
StringEquals: {
'kms:ViaService': 'secretsmanager.us-east-1.amazonaws.com',
},
},
}));
const secret = new secretsmanager.Secret(this, 'SharedSecret', {
secretName: 'cross-account/database-credentials',
encryptionKey: kmsKey,
});
// Secret'a resource policy ekle
secret.addToResourcePolicy(new iam.PolicyStatement({
sid: 'AllowAccountBAccess',
principals: [new iam.AccountPrincipal('222222222222')],
actions: ['secretsmanager:GetSecretValue', 'secretsmanager:DescribeSecret'],
resources: ['*'],
}));
Implementation - Account B (Secret Consumer)
// CDK code for Account B
const applicationRole = new iam.Role(this, 'ApplicationRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
inlinePolicies: {
'SecretAccess': new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['secretsmanager:GetSecretValue'],
resources: [
'arn:aws:secretsmanager:us-east-1:111111111111:secret:cross-account/database-credentials-*'
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['kms:Decrypt'],
resources: [
'arn:aws:kms:us-east-1:111111111111:key/12345678-1234-1234-1234-123456789012'
],
conditions: {
StringEquals: {
'kms:ViaService': 'secretsmanager.us-east-1.amazonaws.com',
},
},
}),
],
}),
},
});
Warning: Account B’de KMS decrypt permission’ını unutmak yaygın bir hatadır. Secrets Manager policy doğru olsa bile secret retrieval “AccessDeniedException” ile fail olur.
Troubleshooting: CloudTrail’de error code’lu KMS API call’larını kontrol et. “AccessDenied” ile fail olan “Decrypt” operation’larına bak.
Parameter Store Reference Pattern
Parameter Store API’yi standardize ederken gerçek secret’ları Secrets Manager’da saklayabilirsin:
# Reference parameter oluştur
aws ssm put-parameter \
--name "/app/database/password" \
--value "{{resolve:secretsmanager:prod/database:SecretString:password}}" \
--type "String" \
--description "Reference to Secrets Manager secret"
Application code’unda sadece Parameter Store SDK gerekir:
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
const client = new SSMClient({ region: "us-east-1" });
const command = new GetParameterCommand({
Name: "/app/database/password",
WithDecryption: true,
});
const response = await client.send(command);
console.log(response.Parameter.Value); // Secrets Manager'dan gerçek secret
Fayda: Basitleştirilmiş application code, tek API surface area, servisler arası daha kolay migration path.
Container Secrets Injection
Container’lara secret inject etmek için birden fazla pattern var, her birinin farklı trade-off’ları var.
Pattern A: Environment Variable Injection (ECS)
Bu native ECS yaklaşımı - secret’lar container startup’ında inject ediliyor.
// CDK - ECS Task Definition with secrets
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
cpu: 256,
memoryLimitMiB: 512,
});
taskDefinition.addContainer('app', {
image: ecs.ContainerImage.fromRegistry('myapp:latest'),
secrets: {
// Secrets Manager'dan
DB_PASSWORD: ecs.Secret.fromSecretsManager(dbSecret, 'password'),
API_KEY: ecs.Secret.fromSecretsManager(apiKeySecret),
// Parameter Store'dan
CONFIG_VALUE: ecs.Secret.fromSsmParameter(configParam),
},
logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'app' }),
});
// Read permission'ları ver
dbSecret.grantRead(taskDefinition.taskRole);
apiKeySecret.grantRead(taskDefinition.taskRole);
configParam.grantRead(taskDefinition.taskRole);
Raw Task Definition JSON:
{
"family": "myapp",
"taskRoleArn": "arn:aws:iam::123456789012:role/myapp-task-role",
"containerDefinitions": [
{
"name": "app",
"image": "myapp:latest",
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/database-AbCdEf:password::"
},
{
"name": "API_KEY",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:api-key-XyZaBc"
},
{
"name": "CONFIG_VALUE",
"valueFrom": "arn:aws:ssm:us-east-1:123456789012:parameter/app/config"
}
]
}
]
}
Warning: Secret’lar SADECE container startup’ında inject edilir. Rotate edilen secret’lar container restart (yeni task launch) gerektirir.
Pattern B: Runtime Retrieval with Caching
Restart olmadan rotation handle etmesi gereken uygulamalar için:
// Application code - runtime'da secret'ları al
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
class SecretCache {
private cache: Map<string, { value: string; expiry: number }> = new Map();
private client: SecretsManagerClient;
private ttlMs: number;
constructor(ttlMs: number = 300000) { // 5 dakika default
this.client = new SecretsManagerClient({});
this.ttlMs = ttlMs;
}
async getSecret(secretId: string): Promise<string> {
const now = Date.now();
const cached = this.cache.get(secretId);
if (cached && cached.expiry > now) {
return cached.value;
}
const command = new GetSecretValueCommand({ SecretId: secretId });
const response = await this.client.send(command);
const value = response.SecretString!;
this.cache.set(secretId, {
value,
expiry: now + this.ttlMs,
});
return value;
}
}
// Lambda handler örneği
const secretCache = new SecretCache(300000); // 5 dakika cache
export const handler = async (event: any) => {
const dbPassword = await secretCache.getSecret(process.env.DB_SECRET_ARN!);
// Password'ü database connection için kullan
};
Maliyet Analizi:
- Startup injection: Container başına 1 API call (~$0.05/10,000 call)
- 5 dakikalık cache ile runtime retrieval: Container başına
288 API call/gün ($1.44/ay) - Her request’te runtime retrieval: Binlerce API call olası (pahalı, tavsiye edilmez)
Pattern C: AWS Parameters and Secrets Lambda Extension
Lambda function’lar için built-in caching sağlayan extension’ı kullan:
// Extension kullanan Lambda function
export const handler = async (event: any) => {
// Extension sidecar olarak çalışır, local HTTP endpoint sağlar
const response = await fetch(
`http://localhost:2773/secretsmanager/get?secretId=${process.env.SECRET_ARN}`,
{
headers: {
'X-Aws-Parameters-Secrets-Token': process.env.AWS_SESSION_TOKEN!,
},
}
);
const secret = await response.json();
return { dbPassword: JSON.parse(secret.SecretString).password };
};
Faydaları:
- Built-in caching (API call’ları ~%90 azaltır)
- Caching için application logic’te değişiklik gerektirmez
- Hem Secrets Manager hem Parameter Store’u destekler
Deployment:
const lambdaFunction = new lambda.Function(this, 'Function', {
// ... diğer config
layers: [
lambda.LayerVersion.fromLayerVersionArn(
this,
'ParametersAndSecretsLayer',
`arn:aws:lambda:${this.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11`
),
],
environment: {
PARAMETERS_SECRETS_EXTENSION_CACHE_ENABLED: 'true',
PARAMETERS_SECRETS_EXTENSION_CACHE_SIZE: '1000',
PARAMETERS_SECRETS_EXTENSION_HTTP_PORT: '2773',
},
});
Tip: Lambda extension API call’ları %99 azaltır. Yüksek trafikli function’lar için maliyet 0.05/ay’a düşer.
EKS Secrets with CSI Driver
EKS üzerinde Kubernetes workload’ları için native entegrasyon sağlayan Secrets Store CSI Driver’ı kullan.
Architecture Setup
# 1. Secrets Store CSI Driver'ı yükle
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver \
--namespace kube-system
# 2. AWS Provider'ı yükle
kubectl apply -f https://raw.githubusercontent.com/aws/secrets-store-csi-driver-provider-aws/main/deployment/aws-provider-installer.yaml
SecretProviderClass Configuration
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: aws-secrets
namespace: production
spec:
provider: aws
parameters:
objects: |
- objectName: "prod/database"
objectType: "secretsmanager"
objectAlias: "db-creds"
jmesPath:
- path: username
objectAlias: db-username
- path: password
objectAlias: db-password
- objectName: "/app/config/api-endpoint"
objectType: "ssmparameter"
objectAlias: "api-endpoint"
# Opsiyonel: Kubernetes Secret'a sync et
secretObjects:
- secretName: database-secret
type: Opaque
data:
- objectName: db-username
key: username
- objectName: db-password
key: password
IRSA ile Pod Configuration
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service-account
namespace: production
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/app-secrets-access
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: app-service-account
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets"
readOnly: true
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: database-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: database-secret
key: password
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "aws-secrets"
Pod Identity için IAM Role
// CDK - EKS Pod Identity (IRSA) için IAM role oluştur
const podRole = new iam.Role(this, 'PodSecretsRole', {
assumedBy: new iam.FederatedPrincipal(
cluster.openIdConnectProvider.openIdConnectProviderArn,
{
StringEquals: {
[`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:sub`]:
'system:serviceaccount:production:app-service-account',
[`${cluster.openIdConnectProvider.openIdConnectProviderIssuer}:aud`]:
'sts.amazonaws.com',
},
},
'sts:AssumeRoleWithWebIdentity'
),
});
// Secret'lara access izni ver
dbSecret.grantRead(podRole);
configParam.grantRead(podRole);
// Custom key kullanıyorsan KMS permission'ları
kmsKey.grant(podRole, 'kms:Decrypt');
Önemli Fark - IRSA vs Pod Identity:
- IRSA (eski metod): OIDC provider setup gerektirir, EKS 1.17+ ile çalışır
- Pod Identity (yeni metod, 2024+): Basitleştirilmiş setup, daha iyi performance, EKS 1.24+ gerektirir
Secret Rotation Implementation
Otomatik rotation Secrets Manager’ın önemli faydalarından biri. İşte doğru implementation:
Rotation Flow
Lambda Rotation Function - RDS MySQL
# Lambda rotation function
import boto3
import json
import pymysql
import os
secrets_client = boto3.client('secretsmanager')
def lambda_handler(event, context):
secret_arn = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
# Uygun step'e dispatch et
if step == "createSecret":
create_secret(secrets_client, secret_arn, token)
elif step == "setSecret":
set_secret(secrets_client, secret_arn, token)
elif step == "testSecret":
test_secret(secrets_client, secret_arn, token)
elif step == "finishSecret":
finish_secret(secrets_client, secret_arn, token)
else:
raise ValueError(f"Invalid step: {step}")
def create_secret(client, arn, token):
# AWSPENDING label'li version zaten var mı kontrol et
try:
client.get_secret_value(
SecretId=arn,
VersionStage="AWSPENDING",
VersionId=token
)
print("Secret version zaten mevcut")
return
except client.exceptions.ResourceNotFoundException:
pass
# Mevcut secret'ı al
current_secret = client.get_secret_value(
SecretId=arn,
VersionStage="AWSCURRENT"
)
secret_dict = json.loads(current_secret['SecretString'])
# Yeni password oluştur
new_password = client.get_random_password(
ExcludeCharacters='/@"\'\\',
PasswordLength=32,
ExcludePunctuation=False,
RequireEachIncludedType=True
)
secret_dict['password'] = new_password['RandomPassword']
# AWSPENDING label ile yeni secret'ı sakla
client.put_secret_value(
SecretId=arn,
ClientRequestToken=token,
SecretString=json.dumps(secret_dict),
VersionStages=['AWSPENDING']
)
def set_secret(client, arn, token):
# Pending secret'ı al
pending_secret = client.get_secret_value(
SecretId=arn,
VersionStage="AWSPENDING",
VersionId=token
)
pending_dict = json.loads(pending_secret['SecretString'])
# Connection için current secret'ı al
current_secret = client.get_secret_value(
SecretId=arn,
VersionStage="AWSCURRENT"
)
current_dict = json.loads(current_secret['SecretString'])
# Current credential'lar ile database'e bağlan
connection = pymysql.connect(
host=current_dict['host'],
user=current_dict['username'],
password=current_dict['password'],
database=current_dict['dbname'],
connect_timeout=5
)
try:
with connection.cursor() as cursor:
# Database'de password'u güncelle
sql = f"ALTER USER '{pending_dict['username']}'@'%' IDENTIFIED BY %s"
cursor.execute(sql, (pending_dict['password'],))
connection.commit()
finally:
connection.close()
def test_secret(client, arn, token):
# Pending secret'ı al
pending_secret = client.get_secret_value(
SecretId=arn,
VersionStage="AWSPENDING",
VersionId=token
)
pending_dict = json.loads(pending_secret['SecretString'])
# Yeni credential'lar ile connection'ı test et
connection = pymysql.connect(
host=pending_dict['host'],
user=pending_dict['username'],
password=pending_dict['password'],
database=pending_dict['dbname'],
connect_timeout=5
)
try:
with connection.cursor() as cursor:
# Access'i verify etmek için basit query çalıştır
cursor.execute("SELECT 1")
result = cursor.fetchone()
if result[0] != 1:
raise ValueError("Test query başarısız")
finally:
connection.close()
def finish_secret(client, arn, token):
# AWSCURRENT label'i yeni versiona taşı
metadata = client.describe_secret(SecretId=arn)
current_version = None
for version, stages in metadata['VersionIdsToStages'].items():
if "AWSCURRENT" in stages:
if version == token:
# Zaten current, yapılacak bir şey yok
return
current_version = version
break
# Version stage'lerini güncelle
client.update_secret_version_stage(
SecretId=arn,
VersionStage="AWSCURRENT",
MoveToVersionId=token,
RemoveFromVersionId=current_version
)
CDK Setup for Rotation
Built-in destekli RDS database’leri için:
// RDS database
const dbInstance = new rds.DatabaseInstance(this, 'Database', {
engine: rds.DatabaseInstanceEngine.mysql({
version: rds.MysqlEngineVersion.VER_8_0
}),
vpc,
credentials: rds.Credentials.fromGeneratedSecret('admin'),
});
// Secret'a rotation ekle
dbInstance.secret!.addRotationSchedule('RotationSchedule', {
automaticallyAfter: cdk.Duration.days(30),
hostedRotation: secretsmanager.HostedRotation.mysqlSingleUser(),
});
Custom uygulamalar için:
// Rotation için Lambda function
const rotationLambda = new lambda.Function(this, 'RotationFunction', {
runtime: lambda.Runtime.PYTHON_3_12,
handler: 'rotation.lambda_handler',
code: lambda.Code.fromAsset('lambda/rotation'),
timeout: cdk.Duration.minutes(5),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});
// Permission'ları ver
dbSecret.grantRead(rotationLambda);
dbSecret.grantWrite(rotationLambda);
// Rotation'ı ekle
dbSecret.addRotationSchedule('CustomRotation', {
rotationLambda,
automaticallyAfter: cdk.Duration.days(30),
});
Warning: Yaygın hatalar:
- Network Access: Lambda’nın database’e erişmek için VPC access’e ihtiyacı var. VPC subnet’leri doğru configure et.
- Timeout: Default 3 saniye çok kısa. Rotation için 5 dakikaya ayarla.
- Permission’lar: Lambda’nın secret’a hem read hem write ve KMS decrypt/encrypt permission’ları gerekir.
- Idempotency: Yeni oluşturmadan önce AWSPENDING version’ının var olup olmadığını her zaman kontrol et.
- Connection Pooling: Eski password kullanan açık connection’lar otomatik olarak yeni password’u almaz. Uygulamalar connection refresh’i handle etmeli.
Alternating Users Strategy
High-availability uygulamalarda zero-downtime rotation için:
Architecture:
- İki database user:
app_user_aveapp_user_b - Her iki user’ın da identik permission’ları var
- Rotation hangi user’ın password’ünün güncelleneceğini alterne eder
- Uygulama rotation sırasında her zaman bir geçerli credential’a sahip
Faydaları:
- Downtime penceresi yok
- Aktif connection’lar rotation sırasında çalışmaya devam eder
- Connection refresh handle edemeyecek uygulamalar için uygun
Trade-off: User’ları clone etmek için ayrı bir secret’ta superuser credential’ları gerektirir.
Multi-Region Secrets Replication
Disaster recovery senaryoları için Secrets Manager otomatik replication destekler.
Replication ile Primary Secret
// Replication ile primary region secret
const primarySecret = new secretsmanager.Secret(this, 'PrimarySecret', {
secretName: 'prod/database-credentials',
description: 'Production database credentials',
replicaRegions: [
{
region: 'us-west-2',
encryptionKey: replicaKmsKey, // Opsiyonel: farklı KMS key kullan
},
{
region: 'eu-west-1',
},
],
});
ARN Yapısı:
- Primary:
arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/database-credentials-AbCdEf - Replica:
arn:aws:secretsmanager:us-west-2:123456789012:secret:prod/database-credentials-AbCdEf
Önemli Noktalar:
- Secret suffix (
-AbCdEf) region’lar arasında identik - Replication otomatik ve near real-time
- Primary region’daki rotation replica’lara propagate olur
- Her replica ayrı secret olarak faturalanır ($0.40/ay her biri)
- Replica’lar read-only, update’ler primary region’da olmalı
Failover ile Disaster Recovery
// Failover logic ile application code
class SecretService {
private primaryRegion = 'us-east-1';
private replicaRegion = 'us-west-2';
private secretName = 'prod/database-credentials';
async getSecretWithFailover(): Promise<string> {
try {
// Önce primary region'ı dene
return await this.getSecret(this.primaryRegion);
} catch (error) {
console.error('Primary region başarısız, replica deneniyor', error);
// Replica region'a fallback
return await this.getSecret(this.replicaRegion);
}
}
private async getSecret(region: string): Promise<string> {
const client = new SecretsManagerClient({ region });
const command = new GetSecretValueCommand({
SecretId: this.secretName,
});
const response = await client.send(command);
return response.SecretString!;
}
}
Maliyet Optimizasyonu Alternatifi
Replication yerine cross-region secret access kullan (daha yüksek latency, daha düşük maliyet):
// us-west-2 application'dan us-east-1'deki secret'a eriş
const client = new SecretsManagerClient({ region: 'us-east-1' });
const command = new GetSecretValueCommand({
SecretId: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/database-credentials-AbCdEf',
});
const response = await client.send(command);
Trade-off: Replica başına $0.40/ay tasarruf eder ama cross-region API latency ekler (~50-150ms).
Break-Glass Emergency Access
Emergency access prosedürleri incident response için kritik. İşte güvenli implementation:
Break-Glass Role Architecture
// CDK - Tüm account'larda break-glass IAM role
export class BreakGlassStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Geniş permission'lara sahip break-glass role
const breakGlassRole = new iam.Role(this, 'BreakGlassRole', {
roleName: 'BREAK-GLASS-EMERGENCY-ACCESS',
description: 'Incident response için emergency access role',
maxSessionDuration: cdk.Duration.hours(2), // Kısa session
assumedBy: new iam.AccountPrincipal('111111111111'), // Management account
});
// Tüm secret'lara access ver
breakGlassRole.addToPolicy(new iam.PolicyStatement({
sid: 'SecretsManagerEmergencyAccess',
effect: iam.Effect.ALLOW,
actions: [
'secretsmanager:GetSecretValue',
'secretsmanager:DescribeSecret',
'secretsmanager:ListSecrets',
],
resources: ['*'],
}));
// Tüm key'ler için KMS decrypt ver
breakGlassRole.addToPolicy(new iam.PolicyStatement({
sid: 'KMSDecryptEmergencyAccess',
effect: iam.Effect.ALLOW,
actions: ['kms:Decrypt', 'kms:DescribeKey'],
resources: ['*'],
conditions: {
StringEquals: {
'kms:ViaService': [
`secretsmanager.${this.region}.amazonaws.com`,
`ssm.${this.region}.amazonaws.com`,
],
},
},
}));
// Parameter Store access ver
breakGlassRole.addToPolicy(new iam.PolicyStatement({
sid: 'ParameterStoreEmergencyAccess',
effect: iam.Effect.ALLOW,
actions: [
'ssm:GetParameter',
'ssm:GetParameters',
'ssm:GetParametersByPath',
],
resources: ['*'],
}));
}
}
Break-Glass Access Monitoring
// Break-glass access'i detect etmek için EventBridge rule
const breakGlassAlertRule = new events.Rule(this, 'BreakGlassAlert', {
eventPattern: {
source: ['aws.sts'],
detailType: ['AWS API Call via CloudTrail'],
detail: {
eventName: ['AssumeRole'],
requestParameters: {
roleArn: [{
prefix: 'arn:aws:iam::*:role/BREAK-GLASS-EMERGENCY-ACCESS'
}],
},
},
},
});
// Security team için SNS topic
const securityTopic = new sns.Topic(this, 'SecurityAlertTopic', {
displayName: 'Critical Security Alerts',
});
breakGlassAlertRule.addTarget(new targets.SnsTopic(securityTopic, {
message: events.RuleTargetInput.fromEventPath(
'$.detail.userIdentity.principalId break-glass role\'u assume etti'
),
}));
Emergency Access Prosedürü
Aktivasyon:
- Security team break-glass password’ü fiziksel kasadan alır
- İkinci kişi YubiKey’i ayrı güvenli konumdan alır
- İkisi de mevcut olmalı (two-person rule)
Access:
# Break-glass user ile AWS CLI'yi configure et
aws configure --profile break-glass
# Target account'ta role assume et
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/BREAK-GLASS-EMERGENCY-ACCESS \
--role-session-name "incident-2024-11-30-database-outage" \
--serial-number arn:aws:iam::111111111111:mfa/EMERGENCY-BREAK-GLASS \
--token-code 123456 \
--profile break-glass
# Temporary credential'ları export et
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
# Secret'lara eriş
aws secretsmanager get-secret-value \
--secret-id prod/database-credentials \
--query SecretString \
--output text
Post-Incident:
- Temporary credential’ları hemen revoke et
- Erişilen tüm secret’ları 4 saat içinde rotate et
- Alınan tüm aksiyonları incident report’a dokümante et
- Complete audit trail için CloudTrail log’larını review et
- Neden break-glass’a ihtiyaç duyulduğu konusunda post-mortem yap
Audit Logging with CloudTrail
Kapsamlı audit logging güvenlik ve uyumluluk için esansiyel.
CloudTrail Configuration
// CloudTrail log'ları için S3 bucket
const trailBucket = new s3.Bucket(this, 'CloudTrailBucket', {
bucketName: `cloudtrail-logs-${this.account}`,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
versioned: true,
lifecycleRules: [
{
transitions: [
{
storageClass: s3.StorageClass.INTELLIGENT_TIERING,
transitionAfter: cdk.Duration.days(30),
},
{
storageClass: s3.StorageClass.GLACIER,
transitionAfter: cdk.Duration.days(90),
},
],
expiration: cdk.Duration.days(2555), // Uyumluluk için 7 yıl
},
],
});
// CloudTrail trail
const trail = new cloudtrail.Trail(this, 'SecurityAuditTrail', {
bucket: trailBucket,
includeGlobalServiceEvents: true,
isMultiRegionTrail: true,
enableFileValidation: true,
sendToCloudWatchLogs: true,
});
// Secrets Manager için data event'leri
trail.addEventSelector({
readWriteType: cloudtrail.ReadWriteType.ALL,
includeManagementEvents: true,
dataResources: [
{
type: 'AWS::SecretsManager::Secret',
values: ['arn:aws:secretsmanager:*:*:secret:*'],
},
],
});
Warning: CloudTrail default olarak sadece management event’leri log’lar. Data event’leri (
GetSecretValuedahil) explicit olarak enable edilmeli.Maliyet Etkisi: ~0.01/ay.
Analiz için Athena Query’leri
-- Tüm secret access event'lerini bul
SELECT
eventTime,
userIdentity.principalId,
userIdentity.arn,
eventName,
json_extract_scalar(requestParameters, '$.secretId') as secretId,
sourceIPAddress,
errorCode
FROM cloudtrail_logs
WHERE eventSource = 'secretsmanager.amazonaws.com'
AND eventName IN ('GetSecretValue', 'PutSecretValue', 'DeleteSecret')
AND year = '2025' AND month = '12' AND day = '01'
ORDER BY eventTime DESC;
-- Cross-account secret access
SELECT
eventTime,
userIdentity.accountId as callerAccount,
json_extract_scalar(requestParameters, '$.secretId') as secretArn,
eventName,
errorCode
FROM cloudtrail_logs
WHERE eventSource = 'secretsmanager.amazonaws.com'
AND userIdentity.accountId != regexp_extract(
json_extract_scalar(requestParameters, '$.secretId'),
'arn:aws:secretsmanager:[^:]+:([^:]+):',
1
)
AND year = '2025' AND month = '12'
ORDER BY eventTime DESC;
Maliyet Analizi & Optimizasyon
Maliyet yapısını anlamak, güvenlikten ödün vermeden harcamayı optimize etmene yardımcı olur.
Detaylı Maliyet Senaryoları
Senaryo 1: 10 Statik API Key (Rotation Yok)
- Parameter Store Standard: $0/ay (ücretsiz tier)
- Secrets Manager: $4.00/ay
- Tavsiye: Parameter Store Standard
- Tasarruf: $4.00/ay
Senaryo 2: 5 RDS Password (Aylık Rotation)
- Parameter Store: $0.25/ay + manuel rotation iş gücü + downtime riski
- Secrets Manager: 0 rotation = $2.00/ay
- Tavsiye: Secrets Manager
- ROI: Otomasyon maliyete değer
Senaryo 3: Yüksek Trafikli Lambda
- Extension olmadan: 1M invocation/ay × 1 API call = $5.00/ay
- Extension ile: API call’lar %99 azaldı = $0.05/ay
- Tasarruf: $4.95/ay (%99 azalma)
Maliyet Optimizasyon Stratejileri
Strateji 1: Hybrid Yaklaşım
Statik configuration için Parameter Store, rotating credential’lar için Secrets Manager kullan:
// Statik değerler Parameter Store'da (ücretsiz)
const apiEndpoint = ssm.StringParameter.fromStringParameterAttributes(
this, 'ApiEndpoint', {
parameterName: '/app/api/endpoint',
}
);
// Rotating credential'lar Secrets Manager'da (ücretli)
const dbSecret = secretsmanager.Secret.fromSecretNameV2(
this, 'DbSecret',
'prod/database'
);
Strateji 2: Secret’ları Consolidate Et
Her credential component için ayrı secret yerine:
// Yanlış: Birden fazla secret ($1.20/ay)
const dbUsername = new secretsmanager.Secret(this, 'DbUser');
const dbPassword = new secretsmanager.Secret(this, 'DbPass');
const dbHost = new secretsmanager.Secret(this, 'DbHost');
// Doğru: Tek secret ($0.40/ay)
const dbCredentials = new secretsmanager.Secret(this, 'DbCreds', {
secretObjectValue: {
username: cdk.SecretValue.unsafePlainText('admin'),
password: cdk.SecretValue.unsafePlainText('generated'),
host: cdk.SecretValue.unsafePlainText('db.example.com'),
port: cdk.SecretValue.unsafePlainText('3306'),
},
});
// Tasarruf: Database başına $0.80/ay
Strateji 3: Seçici Replication
Sadece kritik production secret’ları replicate et:
// Sadece kritik production database secret'larını replicate et
if (secretName.includes('/prod/database') || secretName.includes('/prod/auth')) {
secret.addReplicaRegion('us-west-2', replicaKmsKey);
}
// Kritik olmayan secret'lar: cross-region API call'ları kullan
// Kabul edilebilir: API key'ler, statik token'lar, config değerleri
Maliyet Analizi:
- 20 secret, 2 replica: $24/ay
- 5 kritik replicated + 15 sadece primary: $10/ay
- Tasarruf: $14/ay (%58 azalma)
Yaygın Hatalar & Çözümler
Karşılaştığım teknik sorunlar ve nasıl çözüldükleri:
Hata 1: Cross-Account Access için Default KMS Key
Problem: Default aws/secretsmanager key kullanırken cross-account sharing “AccessDeniedException” ile fail oluyor.
Root Cause: AWS-managed key’lerin policy’leri cross-account access için modifiye edilemiyor.
Çözüm: Her zaman customer-managed KMS key’ler oluştur:
// Yanlış: Default key kullanıyor
const secret = new secretsmanager.Secret(this, 'Secret', {
secretName: 'shared-secret',
});
// Doğru: Customer-managed key
const kmsKey = new kms.Key(this, 'SharedSecretKey', {
enableKeyRotation: true,
});
kmsKey.addToResourcePolicy(/* cross-account policy */);
const secret = new secretsmanager.Secret(this, 'Secret', {
secretName: 'shared-secret',
encryptionKey: kmsKey,
});
Hata 2: Rotation için Lambda VPC Configuration
Problem: Rotation Lambda VPC’deki RDS’e bağlanırken timeout oluyor.
Root Cause: Lambda VPC access ile configure edilmemiş.
Çözüm:
const rotationLambda = new lambda.Function(this, 'RotationFunction', {
// ...
vpc: database.vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
securityGroups: [rotationSecurityGroup],
timeout: cdk.Duration.minutes(5), // Default 3 saniye değil
});
// Lambda'nın RDS'e erişmesine izin ver
rotationSecurityGroup.addEgressRule(
database.connections.securityGroups[0],
ec2.Port.tcp(3306),
'Rotation Lambda\'nin database\'e erişimine izin ver'
);
Hata 3: ECS Secret’ları Sadece Startup’ta Inject Ediliyor
Problem: Rotation sonrası container’lar authentication error ile fail oluyor.
Root Cause: ECS secret’ları sadece startup’ta inject ediyor.
Çözüm: Graceful connection handling implement et:
class DatabaseConnection {
private pool: any;
private secretId: string;
private secretCache: { value: string; expiry: number } | null = null;
async getConnection() {
try {
return await this.pool.getConnection();
} catch (error) {
if (this.isAuthError(error)) {
console.log('Auth error detect edildi, secret refresh ediliyor');
await this.refreshSecret();
this.pool = this.createPool(this.secretCache!.value);
return await this.pool.getConnection();
}
throw error;
}
}
private async refreshSecret() {
const command = new GetSecretValueCommand({ SecretId: this.secretId });
const response = await secretsClient.send(command);
this.secretCache = {
value: response.SecretString!,
expiry: Date.now() + 300000, // 5 dakika
};
}
}
Hata 4: Aşırı Lambda API Çağrıları
Problem: Secrets Manager maliyetleri ayda $50+ seviyesine çıkıyor.
Root Cause: Her invocation’da secret fetch ediliyor, caching yok.
Çözüm: Lambda Extension kullan (Pattern C’de gösterildi).
Sonuç: %99 maliyet azalması.
Hata 5: Eksik CloudTrail Data Event’leri
Problem: GetSecretValue operasyonları için audit trail yok.
Root Cause: Data event’ler varsayılan olarak enable değil.
Çözüm: Data event logging’i enable et (Audit Logging bölümünde gösterildi).
Hata 6: Gizli Olmayan Config’i Secrets Manager’da Saklamak
Problem: Gizli olmayan değerler için ayda $0.40 ödeniyor.
Çözüm: Karar framework’ü kullan:
Hassas mı? (şifre, API key, token)
├─ EVET → Rotate edilebilir mi?
│ ├─ EVET → Secrets Manager ($0.40/ay)
│ └─ HAYIR → Parameter Store SecureString (ücretsiz)
└─ HAYIR → Parameter Store Standard (ücretsiz)
Önemli Çıkarımlar
AWS secrets management ile çalışmak bana şu önemli dersleri öğretti:
-
Servis Seçimi Use Case Hakkında: Secrets Manager’ı rotating credential’lar için ayır. Geri kalan her şey için Parameter Store kullan. Bu basit kural maliyetlerde %80 tasarruf sağlayabilir.
-
Cross-Account Access Customer-Managed Key Gerektirir: Default
aws/secretsmanagerkey çalışmaz. Migration acısından kaçınmak için ilk günden customer-managed KMS key’ler oluştur. -
Container Injection Tek Seferlik: Startup’ta inject edilen secret’lar rotation’da güncellenmez. Uygulamaları connection refresh handle edecek şekilde tasarla veya alternating-users strategy kullan.
-
Lambda Extension Maliyeti %99 Azaltır: Yüksek trafikli Lambda function’lar için extension’ın built-in caching’i esansiyel. Tek satırlık bir ekleme önemli para tasarrufu sağlar.
-
CloudTrail Data Event’leri Kritik: İlk günden enable et. Maliyet ihmal edilebilir (~$0.10 per 100,000 event) ama audit değeri ölçülemez.
-
Multi-Region Replication İş Kararı: Her şeyi replicate etme. RTO/RPO requirement’larını analiz et ve sadece kritik secret’ları replicate et. Cross-region API call’lar genellikle kabul edilebilir.
-
Break-Glass Prosedürleri Test Edilmeli: Test edilmemiş emergency access incident’lar sırasında işe yaramaz. Hem teknik hem organizasyonel hazırlığı validate etmek için quarterly test yap.
-
Otomasyon Süreci Yener: Manuel rotation mühendis zamanında ayda 4’a mal olur. ROI anında gerçekleşir.
Anahtar güvenlik, maliyet ve operasyonel kompleksiteyi dengelemek. Statik config için Parameter Store ile basit başla, sensitive credential’ları Secrets Manager’a migrate et, database’ler için rotation implement et ve sadece gerektiğinde cross-region replication ekle.
İlgili yazılar
Global uygulamalar için AWS edge computing çözümlerini seçme ve uygulama üzerine pratik örnekler ve maliyet optimizasyonu stratejileri içeren kapsamlı teknik rehber.
Amazon Cognito'nun gelişmiş özellikleri üzerine kapsamlı teknik kılavuz: özel authentication akışları, federation pattern'leri, multi-tenancy mimarileri, migration stratejileri ve production-grade güvenlik implementasyonu.
Dev, staging ve production ortamlarında Lambda Layer versiyonlarını yönetmek için pratik yaklaşımlar. AWS CDK implementasyonları, otomatik deployment pipeline'ları ve rollback stratejileri ile.
Event-driven mimarileri CDK'ya taşıma. EventBridge, SQS, SNS entegrasyonları ve pattern'ler.
Native AWS servisleri, otomasyon ve kanıtlanmış implementation pattern'leri kullanarak AWS maliyetlerini %40-70 azaltmaya yönelik kapsamlı bir rehber.