2025-09-04
Serverless Framework'ten AWS CDK'ya Geçiş: Bölüm 5 - Authentication, Authorization ve IAM
Serverless Framework'ten AWS CDK'ya geçerken Cognito ile güçlü kimlik doğrulama, API Gateway authorizer'lar ve ince taneli IAM politikaları uygulama.
Serverless Framework’ten AWS CDK’ya authentication ve authorization migrasyonu, hem güvenlik duruşunu hem de uygulama performansını etkileyebilecek benzersiz zorluklar sunar. Organizasyonlar genellikle Serverless Framework implementasyonlarının organik büyüme ve hızlı iterasyon döngüleri yoluyla güvenlik borcu biriktirdiğini keşfederler.
Yaygın pattern’lar arasında aşırı geniş IAM izinleri olan fonksiyonlar, multiple custom authorizer’lara dağılmış authorization logic’i ve erişim kontrol kararları için yetersiz audit trail’ler bulunur. Bu sorunlar migration değerlendirmeleri sırasında belirgin hale gelir ve compliance gereksinimlerini önemli ölçüde etkileyebilir.
Bu guide, migration süreci boyunca uygulama erişilebilirliğini korurken AWS CDK ile enterprise-grade authentication ve authorization yeniden inşa etmeyi kapsar.
Seri Navigasyonu:
- Bölüm 1: Neden Geçiş Yapalım?
- Bölüm 2: CDK Environment Kurulumu
- Bölüm 3: Lambda Fonksiyonları ve API Gateway Migration
- Bölüm 4: Database ve Environment Management
- Bölüm 5: Authentication, Authorization ve IAM (bu yazı)
- Bölüm 6: Migration Stratejileri ve Best Practice’ler
Authentication Migration Zorluklarını Anlama
Çözümler implement etmeden önce, mevcut authentication pattern’larını değerlendirmek önemlidir. Migration değerlendirmeleri sırasında keşfedilen yaygın sorunlar şunları içerir. Audit sonrası bu sorunları tek seferde çözmek yerine öncelik sırasına koymak; önce güvenlik risklerini, sonra performans iyileştirmelerini ele almak genellikle daha verimli olur.
Yaygın Serverless Framework Authentication Pattern’ları
User Management: Environment’lar boyunca üç farklı Cognito pool, manuel oluşturulmuş, custom attribute’ların sıfır dokümantasyonu.
Authorization: 12 farklı Lambda authorizer, her biri farklı JWT validation logic’i, caching yok, ortalama 400ms authorization latency.
IAM İzinleri: 47 Lambda fonksiyonu wildcard izinlerle. En kritik payment fonksiyonumuz tüm DynamoDB tablolarına "*" erişime sahipti.
Secret’lar: Environment variable’larda hardcode edilmiş API key’ler, environment’lar arası paylaşılmış, son rotasyon “uzun zaman önce.”
Audit Trail: Hiç. Authorization kararlarının sıfır loglama’sı. “Kim neye ne zaman erişti” sorusuna cevap verecek yol yok.
Migration Etki Değerlendirmeleri
- Compliance riski: Aşırı geniş data erişimi için potansiyel regülatuvar cezalar
- Performans etkisi: 400ms ortalama authorization latency (toplam request zamanının 28%‘i)
- Operasyonel overhead: Authentication sorunlarını çözmek için haftada 3 saat
- Güvenlik borcu: Gereksiz izinleri olan 47 fonksiyon
Production-Grade Cognito Implementation
Audit’ten sonra authentication’ı enterprise kontrolleriyle yeniden inşa ettik. İşte battle-tested yaklaşım:
# serverless.yml
resources:
Resources:
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: ${self:service}-${opt:stage}-users
Schema:
- Name: email
Required: true
Mutable: false
- Name: role
AttributeDataType: String
Mutable: true
AutoVerifiedAttributes:
- email
Policies:
PasswordPolicy:
MinimumLength: 8
RequireUppercase: true
RequireLowercase: true
RequireNumbers: true
RequireSymbols: true
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: ${self:service}-${opt:stage}-client
UserPoolId: !Ref UserPool
GenerateSecret: false
ExplicitAuthFlows:
- ALLOW_USER_PASSWORD_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
Enterprise-Grade CDK Yaklaşımı
İşte SOC 2 audit’ini geçen ve 180K+ kullanıcıyı handle eden Cognito implementasyonu:
// lib/constructs/auth/production-cognito.ts
import {
UserPool,
UserPoolClient,
AccountRecovery,
Mfa,
UserPoolOperation,
StringAttribute,
ClientAttributes,
OAuthScope,
UserPoolDomain,
CognitoUserPoolsAuthorizer
} from 'aws-cdk-lib/aws-cognito';
import { Duration, RemovalPolicy, Tags } from 'aws-cdk-lib';
import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs';
import { Alarm, Metric, TreatMissingData } from 'aws-cdk-lib/aws-cloudwatch';
export class ProductionCognitoAuth extends Construct {
public readonly userPool: UserPool;
public readonly userPoolClient: UserPoolClient;
public readonly authorizer: CognitoUserPoolsAuthorizer;
constructor(scope: Construct, id: string, props: {
stage: string;
domainPrefix?: string;
callbackUrls?: string[];
api: RestApi;
}) {
super(scope, id);
// Audit-uyumlu ayarlarla user pool oluştur
this.userPool = new UserPool(this, 'EnterpriseUserPool', {
userPoolName: `my-service-${props.stage}-users-v2`,
// Gelişmiş güvenlik: production'da self-signup yok
selfSignUpEnabled: props.stage !== 'prod',
signInAliases: {
email: true,
username: false, // Email-only sign-in attack surface'i azaltır
},
signInCaseSensitive: false,
autoVerify: { email: true },
// SOC 2 uyumlu şifre politikası
passwordPolicy: {
minLength: 14, // Audit'ten sonra 12'den artırıldı
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: true,
tempPasswordValidity: Duration.hours(24), // 3 günden azaltıldı
},
// RBAC için kapsamlı user attribute'ları
standardAttributes: {
email: { required: true, mutable: false },
givenName: { required: true, mutable: true },
familyName: { required: true, mutable: true },
},
customAttributes: {
// Role-based access control
role: new StringAttribute({ mutable: true }),
department: new StringAttribute({ mutable: true }),
accessLevel: new StringAttribute({ mutable: true }),
// Audit trail attribute'ları
lastLoginDate: new StringAttribute({ mutable: true }),
createdBy: new StringAttribute({ mutable: false }),
// Compliance attribute'ları
dataAccessLevel: new StringAttribute({ mutable: true }),
complianceFlags: new StringAttribute({ mutable: true }),
},
// Enterprise güvenlik ayarları
accountRecovery: AccountRecovery.EMAIL_ONLY,
mfa: props.stage === 'prod' ? Mfa.REQUIRED : Mfa.OPTIONAL,
mfaSecondFactor: {
sms: false, // Güvenlik için sadece TOTP
otp: true,
},
// Gelişmiş tehdit koruması
advancedSecurityMode: props.stage === 'prod'
? AdvancedSecurityMode.ENFORCED
: AdvancedSecurityMode.AUDIT,
// Markalı iletişimler için email konfigürasyonu
emailSettings: {
from: '[email protected]',
replyTo: '[email protected]',
},
// Güvenlik için device tracking
deviceTracking: {
challengeRequiredOnNewDevice: true,
deviceOnlyRememberedOnUserPrompt: false,
},
// Data protection
removalPolicy: props.stage === 'prod' ? RemovalPolicy.RETAIN : RemovalPolicy.DESTROY,
deletionProtection: props.stage === 'prod',
});
// Enterprise Lambda trigger'ları ekle
this.addSecurityTriggers(props.stage);
// Production app client oluştur
this.userPoolClient = new UserPoolClient(this, 'EnterpriseClient', {
userPool: this.userPool,
userPoolClientName: `my-service-${props.stage}-client-v2`,
// İzin verilen authentication flow'ları
authFlows: {
userPassword: false, // Daha az güvenli flow'u devre dışı bırak
userSrp: true, // Secure Remote Password protocol
custom: true, // Custom auth challenge'lar
adminUserPassword: props.stage !== 'prod', // Admin flow sadece non-prod'da
},
// Enterprise SSO için OAuth konfigürasyonu
oAuth: {
flows: {
authorizationCodeGrant: true,
implicitCodeGrant: false, // Güvenlik için implicit flow'u devre dışı bırak
clientCredentials: false,
},
scopes: [
OAuthScope.EMAIL,
OAuthScope.OPENID,
OAuthScope.PROFILE,
OAuthScope.custom('read:profile'),
OAuthScope.custom('write:profile'),
],
callbackUrls: props.callbackUrls || [],
logoutUrls: [`https://${props.stage === 'prod' ? 'app' : props.stage}.yourcompany.com/logout`],
},
generateSecret: false, // SPA için public client
// Fine-grained attribute erişimi
readAttributes: new ClientAttributes()
.withStandardAttributes({
email: true,
emailVerified: true,
givenName: true,
familyName: true,
})
.withCustomAttributes('role', 'department', 'accessLevel'),
writeAttributes: new ClientAttributes()
.withCustomAttributes('lastLoginDate'), // Sınırlı write erişimi
// Güvenlik odaklı token ayarları
idTokenValidity: Duration.minutes(30), // Güvenlik için kısa ömürlü
accessTokenValidity: Duration.minutes(30), // Güvenlik için kısa ömürlü
refreshTokenValidity: Duration.days(1), // Günlük re-authentication
// Gelişmiş güvenlik seçenekleri
preventUserExistenceErrors: true,
enableTokenRevocation: true,
// Custom token ayarları
authSessionValidity: Duration.minutes(3), // Hızlı auth flow timeout
});
// API Gateway authorizer oluştur
this.authorizer = new CognitoUserPoolsAuthorizer(this, 'CognitoAuthorizer', {
cognitoUserPools: [this.userPool],
authorizerName: `${props.api.restApiName}-cognito-auth`,
identitySource: 'method.request.header.Authorization',
resultsCacheTtl: Duration.minutes(5), // Performans için cache
});
// Markalı deneyim için custom domain ekle
if (props.domainPrefix) {
new UserPoolDomain(this, 'UserPoolDomain', {
userPool: this.userPool,
cognitoDomainPrefix: `${props.domainPrefix}-${props.stage}`,
});
}
// Production monitoring ve alerting
this.addProductionMonitoring(props.stage);
// Compliance tagging
Tags.of(this).add('DataClassification', 'PII');
Tags.of(this).add('Compliance', 'SOC2-GDPR');
Tags.of(this).add('Service', 'authentication');
Tags.of(this).add('Stage', props.stage);
}
private addSecurityTriggers(stage: string) {
// Pre-authentication güvenlik kontrolleri
const preAuthFn = new NodejsFunction(this, 'PreAuthSecurityFunction', {
entry: 'src/auth/triggers/pre-auth-security.ts',
handler: 'handler',
timeout: Duration.seconds(10),
logRetention: RetentionDays.ONE_MONTH,
environment: {
STAGE: stage,
SECURITY_LOG_LEVEL: stage === 'prod' ? 'WARN' : 'DEBUG',
},
});
this.userPool.addTrigger(UserPoolOperation.PRE_AUTHENTICATION, preAuthFn);
// Post-authentication audit logging
const postAuthFn = new NodejsFunction(this, 'PostAuthAuditFunction', {
entry: 'src/auth/triggers/post-auth-audit.ts',
handler: 'handler',
timeout: Duration.seconds(10),
logRetention: RetentionDays.ONE_YEAR, // Audit için uzun retention
environment: {
STAGE: stage,
AUDIT_TABLE: `auth-audit-${stage}`,
},
});
this.userPool.addTrigger(UserPoolOperation.POST_AUTHENTICATION, postAuthFn);
// RBAC kurulumu ile user oluşturma
const postConfirmFn = new NodejsFunction(this, 'PostConfirmationRBACFunction', {
entry: 'src/auth/triggers/post-confirmation-rbac.ts',
handler: 'handler',
timeout: Duration.seconds(30),
environment: {
STAGE: stage,
USERS_TABLE: `users-${stage}`,
ROLES_TABLE: `user-roles-${stage}`,
DEFAULT_ROLE: 'viewer', // Default olarak least privilege
},
});
this.userPool.addTrigger(UserPoolOperation.POST_CONFIRMATION, postConfirmFn);
}
private addProductionMonitoring(stage: string) {
if (stage !== 'prod') return;
// Başarısız authentication alarm'ı
new Alarm(this, 'FailedAuthAlarm', {
metric: new Metric({
namespace: 'AWS/Cognito',
metricName: 'SignInFailures',
dimensionsMap: {
UserPool: this.userPool.userPoolId,
},
statistic: 'Sum',
period: Duration.minutes(5),
}),
threshold: 50, // 5 dakikada 50 başarısız deneme
evaluationPeriods: 1,
treatMissingData: TreatMissingData.NOT_BREACHING,
alarmDescription: 'High number of authentication failures detected',
});
// Compromised credential'lar alarm'ı
new Alarm(this, 'CompromisedCredentialsAlarm', {
metric: new Metric({
namespace: 'AWS/Cognito',
metricName: 'CompromisedCredentialsRisk',
dimensionsMap: {
UserPool: this.userPool.userPoolId,
},
statistic: 'Sum',
period: Duration.minutes(15),
}),
threshold: 1, // Herhangi bir compromised credential kritik
evaluationPeriods: 1,
alarmDescription: 'Compromised credentials detected',
});
}
}
Custom Auth Flow’ları için Lambda Trigger’ları
// src/auth/triggers/pre-signup.ts
import { PreSignUpTriggerEvent, PreSignUpTriggerHandler } from 'aws-lambda';
export const handler: PreSignUpTriggerHandler = async (event) => {
console.log('Pre-signup event:', JSON.stringify(event, null, 2));
// Kurumsal hesaplar için email domain'i doğrula
const email = event.request.userAttributes.email;
const allowedDomains = ['company.com', 'partner.com'];
const domain = email.split('@')[1];
if (!allowedDomains.includes(domain)) {
throw new Error('Registration is restricted to corporate email addresses');
}
// Kurumsal email'leri otomatik onayla
if (domain === 'company.com') {
event.response.autoConfirmUser = true;
event.response.autoVerifyEmail = true;
}
return event;
};
// src/auth/triggers/post-confirmation.ts
import { PostConfirmationTriggerEvent, PostConfirmationTriggerHandler } from 'aws-lambda';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export const handler: PostConfirmationTriggerHandler = async (event) => {
console.log('Post-confirmation event:', JSON.stringify(event, null, 2));
// DynamoDB'de user kaydı oluştur
await client.send(new PutCommand({
TableName: process.env.USERS_TABLE,
Item: {
userId: event.request.userAttributes.sub,
email: event.request.userAttributes.email,
role: event.request.userAttributes['custom:role'] || 'user',
department: event.request.userAttributes['custom:department'],
createdAt: new Date().toISOString(),
status: 'active',
},
}));
return event;
};
400ms Authorization Felaketi
Legacy authorization kurulumumuz performansı öldürüyordu. Her API request şunları gerektiriyordu:
- JWT decode: 50ms
- Cognito JWK fetch: 150ms (caching yok)
- Signature verification: 80ms
- Database role lookup: 120ms
- Toplam authorization zamanı: Request başına 400ms
Business etkisi: Toplam request zamanının 28%‘i authorization’da harcandı. Mobile app “yavaş” olarak algılandı. API responsiveness hakkında customer şikayetleri.
Yüksek Performanslı JWT Authorization
İşte latency’yi 400ms’den 12ms’ye düşüren caching-optimized authorizer:
// lib/constructs/auth/high-performance-jwt-authorizer.ts
import {
TokenAuthorizer,
IdentitySource,
IRestApi
} from 'aws-cdk-lib/aws-apigateway';
import { Duration } from 'aws-cdk-lib';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
export class HighPerformanceJwtAuthorizer extends TokenAuthorizer {
constructor(scope: Construct, id: string, props: {
api: IRestApi;
userPoolId: string;
region: string;
stage: string;
}) {
// Production için optimize edilmiş authorizer fonksiyonu
const authorizerFunction = new NodejsFunction(scope, 'OptimizedAuthorizerFunction', {
entry: 'src/auth/production-jwt-authorizer.ts',
handler: 'handler',
// Tutarlı performans için provisioned concurrency
reservedConcurrentExecutions: props.stage === 'prod' ? 10 : undefined,
timeout: Duration.seconds(5), // Hızlı başarısızlıklar için kısa timeout
memorySize: 512, // JWT processing için optimize edilmiş
logRetention: RetentionDays.ONE_MONTH,
environment: {
USER_POOL_ID: props.userPoolId,
REGION: props.region,
STAGE: props.stage,
// Performans optimizasyon flag'leri
ENABLE_METRICS: props.stage === 'prod' ? 'true' : 'false',
CACHE_TIMEOUT_MS: '300000', // 5 dakika
},
bundling: {
// Daha hızlı cold start'lar için bundle boyutunu minimize et
minify: true,
target: 'node20',
// Sadece gerekli dependency'leri dahil et
nodeModules: ['jsonwebtoken', 'jwk-to-pem'],
externalModules: ['@aws-sdk/*'],
},
});
super(scope, id, {
restApi: props.api,
handler: authorizerFunction,
identitySource: IdentitySource.header('Authorization'),
// Performans için agresif caching
resultsCacheTtl: Duration.minutes(5),
authorizerName: `${props.api.restApiName}-jwt-authorizer-v2`,
// Sıkı token validation
validationRegex: '^Bearer [A-Za-z0-9\\-_=]+\\.[A-Za-z0-9\\-_=]+\\.[A-Za-z0-9\\-_.+/=]*,
});
}
}
// src/auth/production-jwt-authorizer.ts
import { APIGatewayTokenAuthorizerEvent, APIGatewayAuthorizerResult } from 'aws-lambda';
import jwt from 'jsonwebtoken';
import jwkToPem from 'jwk-to-pem';
// Performans için multi-level caching
let cachedKeys: Map<string, string> | null = null;
let cacheTimestamp: number = 0;
const CACHE_TIMEOUT = parseInt(process.env.CACHE_TIMEOUT_MS || '300000');
// Performans metrikleri (production'da toplanır)
const metrics = {
authCount: 0,
keyFetchCount: 0,
cacheHits: 0,
averageLatency: 0,
};
async function getPublicKeys(): Promise<Map<string, string>> {
const now = Date.now();
// Hala geçerliyse cached key'leri döndür
if (cachedKeys && (now - cacheTimestamp) < CACHE_TIMEOUT) {
metrics.cacheHits++;
return cachedKeys;
}
const startTime = Date.now();
metrics.keyFetchCount++;
try {
const jwksUrl = `https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`;
// Timeout ve retry logic ile fetch kullan
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const response = await fetch(jwksUrl, {
signal: controller.signal,
headers: {
'Cache-Control': 'max-age=300', // 5 dakikalık cache iste
},
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`JWK fetch failed: ${response.status}`);
}
const jwks = await response.json();
// JWK'ları dönüştür ve cache'le
cachedKeys = new Map();
jwks.keys.forEach((key: any) => {
try {
cachedKeys!.set(key.kid, jwkToPem(key));
} catch (error) {
console.warn(`Failed to convert JWK ${key.kid}:`, error);
}
});
cacheTimestamp = now;
const fetchTime = Date.now() - startTime;
console.log(`JWK fetch completed in ${fetchTime}ms, cached ${cachedKeys.size} keys`);
return cachedKeys;
} catch (error) {
console.error('JWK fetch failed:', error);
// Fallback olarak stale cache varsa döndür
if (cachedKeys) {
console.warn('Using stale JWK cache due to fetch failure');
return cachedKeys;
}
throw new Error('Unable to fetch signing keys');
}
}
export const handler = async (
event: APIGatewayTokenAuthorizerEvent
): Promise<APIGatewayAuthorizerResult> => {
const startTime = Date.now();
metrics.authCount++;
// Audit trail için gelişmiş request logging
const requestId = Math.random().toString(36).substring(7);
console.log('Authorization request:', {
requestId,
methodArn: event.methodArn,
requestTime: new Date().toISOString(),
sourceIp: event.requestContext?.identity?.sourceIp,
userAgent: event.requestContext?.identity?.userAgent,
});
try {
// Erken token validation
if (!event.authorizationToken || !event.authorizationToken.startsWith('Bearer ')) {
throw new Error('Missing or invalid authorization header format');
}
const token = event.authorizationToken.replace('Bearer ', '');
// Temel token format validation
const tokenParts = token.split('.');
if (tokenParts.length !== 3) {
throw new Error('Invalid JWT format');
}
// Token'ı decode et (henüz signature verify etmiyor)
const decodedToken = jwt.decode(token, { complete: true });
if (!decodedToken || typeof decodedToken === 'string') {
throw new Error('Invalid token structure');
}
// Token expiration'ını erken validate et
const payload = decodedToken.payload as any;
const now = Math.floor(Date.now() / 1000);
if (payload.exp && payload.exp < now) {
throw new Error('Token has expired');
}
if (payload.iat && payload.iat > now + 300) {
throw new Error('Token issued in the future');
}
// Signing key'leri al (cached)
const keys = await getPublicKeys();
const signingKey = keys.get(decodedToken.header.kid!);
if (!signingKey) {
throw new Error(`Signing key not found for kid: ${decodedToken.header.kid}`);
}
// JWT signature ve claim'leri verify et
const verifiedPayload = jwt.verify(token, signingKey, {
algorithms: ['RS256'],
issuer: `https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}`,
audience: payload.aud,
clockTolerance: 30, // 30 saniye clock skew'a izin ver
}) as any;
// User bilgilerini çıkar
const userId = verifiedPayload.sub;
const email = verifiedPayload.email;
const role = verifiedPayload['custom:role'] || 'user';
const accessLevel = verifiedPayload['custom:accessLevel'] || 'basic';
// Resource-specific policy generate et
const policy = generateEnhancedPolicy(
userId,
'Allow',
event.methodArn,
{
userId,
email,
role,
accessLevel,
tokenUse: verifiedPayload.token_use,
authTime: verifiedPayload.auth_time?.toString(),
requestId,
}
);
const totalTime = Date.now() - startTime;
metrics.averageLatency = (metrics.averageLatency + totalTime) / 2;
// Başarılı authorization'ı logla
console.log('Authorization successful:', {
requestId,
userId,
email,
role,
accessLevel,
latency: totalTime,
});
// Metrikleri periyodik olarak raporla
if (metrics.authCount % 100 === 0 && process.env.ENABLE_METRICS === 'true') {
console.log('Authorization metrics:', {
totalAuthorizations: metrics.authCount,
keyFetches: metrics.keyFetchCount,
cacheHitRate: (metrics.cacheHits / metrics.authCount * 100).toFixed(2) + '%',
averageLatency: metrics.averageLatency.toFixed(2) + 'ms',
});
}
return policy;
} catch (error) {
const totalTime = Date.now() - startTime;
console.error('Authorization failed:', {
requestId,
error: error.message,
latency: totalTime,
stackTrace: error.stack,
});
// Non-production'da debugging için
if (process.env.STAGE !== 'prod') {
console.debug('Token details:', {
token: event.authorizationToken,
methodArn: event.methodArn,
});
}
throw new Error('Unauthorized'); // Client'a her zaman generic error döndür
}
};
function generateEnhancedPolicy(
principalId: string,
effect: 'Allow' | 'Deny',
resource: string,
context: Record<string, any>
): APIGatewayAuthorizerResult {
// Daha iyi caching için wildcard resource generate et
const resourceParts = resource.split('/');
const wildcardResource = resourceParts.slice(0, -1).join('/') + '/*';
return {
principalId,
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'execute-api:Invoke',
Effect: effect,
Resource: wildcardResource, // Daha geniş caching'i etkinleştir
},
],
},
context: {
// Tüm context değerlerini string'e dönüştür (API Gateway requirement)
...Object.entries(context).reduce((acc, [key, value]) => ({
...acc,
[key]: String(value || ''),
}), {}),
},
// Stabil kullanıcılar için daha uzun TTL etkinleştir
ttlOverride: context.role === 'admin' ? 300 : 120, // Admin token'ları daha uzun cached
};
}
Group’larla Request-Based Authorizer
// lib/constructs/auth/group-authorizer.ts
export class GroupAuthorizer extends RequestAuthorizer {
constructor(scope: Construct, id: string, props: {
api: IRestApi;
userPoolId: string;
requiredGroups?: string[];
}) {
const authorizerFunction = new NodejsFunction(scope, 'GroupAuthorizerFunction', {
entry: 'src/auth/group-authorizer.ts',
handler: 'handler',
environment: {
USER_POOL_ID: props.userPoolId,
REQUIRED_GROUPS: JSON.stringify(props.requiredGroups || []),
},
});
super(scope, id, {
restApi: props.api,
handler: authorizerFunction,
identitySources: [IdentitySource.header('Authorization')],
resultsCacheTtl: Duration.minutes(5),
authorizerName: `${props.api.restApiName}-group-authorizer`,
});
}
}
Wildcard IAM Felaketi
Güvenlik audit’imiz sırasında payment processing Lambda’mızın bu IAM policy’ye sahip olduğunu keşfettik:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
Çeviri: Payment fonksiyonumuz S3 bucket’larını silebilir, EC2 instance’larını terminate edebilir veya hesabımızdaki herhangi bir DynamoDB tablosuna erişebilirdi. Bir compromised fonksiyon = tam hesap ele geçirme.
Business etkisi: 180K$ potansiyel GDPR cezası, başarısız SOC 2 audit, bloke edilmiş enterprise anlaşmaları.
Least Privilege IAM Mimarisi
İşte güvenlik audit’imizi geçen ve izinleri 94% azaltan role-based sistem:
// lib/constructs/security/lambda-role.ts
import { Role, PolicyStatement, Effect, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
export class LeastPrivilegeLambdaRole extends Role {
constructor(scope: Construct, id: string, props: {
functionName: string;
stage: string;
additionalStatements?: PolicyStatement[];
}) {
super(scope, id, {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
roleName: `${props.functionName}-${props.stage}-role`,
description: `Execution role for ${props.functionName}`,
});
// Temel Lambda izinleri
this.addToPolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: [
'logs:CreateLogGroup',
'logs:CreateLogStream',
'logs:PutLogEvents',
],
resources: [
`arn:aws:logs:*:*:log-group:/aws/lambda/${props.functionName}-*`,
],
}));
// X-Ray tracing
this.addToPolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: [
'xray:PutTraceSegments',
'xray:PutTelemetryRecords',
],
resources: ['*'],
}));
// Custom statement'ları ekle
props.additionalStatements?.forEach(statement => {
this.addToPolicy(statement);
});
}
}
Resource-Based Policy’ler
// lib/constructs/security/resource-policies.ts
export class SecureApiGateway extends RestApi {
constructor(scope: Construct, id: string, props: RestApiProps & {
allowedSourceIps?: string[];
allowedVpcs?: string[];
}) {
super(scope, id, props);
if (props.allowedSourceIps || props.allowedVpcs) {
const conditions: any = {};
if (props.allowedSourceIps) {
conditions['IpAddress'] = {
'aws:SourceIp': props.allowedSourceIps,
};
}
if (props.allowedVpcs) {
conditions['StringEquals'] = {
'aws:SourceVpc': props.allowedVpcs,
};
}
this.addGatewayResponse('UNAUTHORIZED', {
statusCode: '401',
responseHeaders: {
'Access-Control-Allow-Origin': "'*'",
},
templates: {
'application/json': '{"error": "Unauthorized access"}',
},
});
// Resource policy
this.node.addDependency(
new PolicyDocument({
statements: [
new PolicyStatement({
effect: Effect.DENY,
principals: [new AnyPrincipal()],
actions: ['execute-api:Invoke'],
resources: ['execute-api:/*/*/*'],
conditions: {
...conditions,
},
}),
new PolicyStatement({
effect: Effect.ALLOW,
principals: [new AnyPrincipal()],
actions: ['execute-api:Invoke'],
resources: ['execute-api:/*/*/*'],
}),
],
})
);
}
}
}
Cross-Service Authentication
IAM ile Service-to-Service Auth
// lib/constructs/auth/service-auth.ts
export class ServiceAuthFunction extends ServerlessFunction {
constructor(scope: Construct, id: string, props: ServerlessFunctionProps & {
targetServiceUrl: string;
}) {
super(scope, id, {
...props,
environment: {
...props.environment,
TARGET_SERVICE_URL: props.targetServiceUrl,
},
});
// Diğer servisleri invoke etme izni ver
this.addToRolePolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: ['execute-api:Invoke'],
resources: [
`arn:aws:execute-api:${Stack.of(this).region}:*:*/*/*/*`,
],
}));
}
}
// src/libs/service-client.ts
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { Sha256 } from '@aws-crypto/sha256-js';
export class ServiceClient {
private signer: SignatureV4;
constructor(private baseUrl: string) {
this.signer = new SignatureV4({
service: 'execute-api',
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
sessionToken: process.env.AWS_SESSION_TOKEN,
},
sha256: Sha256,
});
}
async request(path: string, method: string, body?: any) {
const url = new URL(path, this.baseUrl);
const signedRequest = await this.signer.sign({
method,
hostname: url.hostname,
path: url.pathname,
protocol: url.protocol,
headers: {
'Content-Type': 'application/json',
host: url.hostname,
},
body: body ? JSON.stringify(body) : undefined,
});
const response = await fetch(url.toString(), {
method,
headers: signedRequest.headers,
body: signedRequest.body,
});
return response.json();
}
}
API Key Management
Güvenli API Key Dağıtımı
// lib/constructs/auth/api-key-manager.ts
export class ApiKeyManager extends Construct {
private keys: Map<string, IApiKey> = new Map();
constructor(scope: Construct, id: string, props: {
api: IRestApi;
stage: string;
}) {
super(scope, id);
// Rate limiting için usage plan
const plan = new UsagePlan(this, 'UsagePlan', {
name: `${props.api.restApiName}-plan`,
throttle: {
rateLimit: 100,
burstLimit: 200,
},
quota: {
limit: 10000,
period: Period.DAY,
},
});
plan.addApiStage({
stage: props.api.deploymentStage,
});
}
createApiKey(name: string, customerId: string): IApiKey {
const key = new ApiKey(this, `ApiKey-${name}`, {
apiKeyName: `${name}-key`,
description: `API key for ${name}`,
customerId,
generateDistinctId: true,
});
// Secrets Manager'da sakla
const secret = new Secret(this, `ApiKeySecret-${name}`, {
secretName: `/api-keys/${name}`,
generateSecretString: {
secretStringTemplate: JSON.stringify({ customerId }),
generateStringKey: 'apiKey',
includeSpace: false,
},
});
// Key value'yu secret ile ilişkilendir
new CustomResource(this, `StoreApiKey-${name}`, {
serviceToken: this.getKeyStorageFunction().functionArn,
properties: {
SecretId: secret.secretArn,
ApiKeyId: key.keyId,
},
});
this.keys.set(name, key);
return key;
}
}
Migration Security Checklist
Authentication Migration
- Cognito user attribute’larını mevcut schema’ya map et
- User migration Lambda trigger implement et
- Password policy uyumluluğunu test et
- MFA ayarlarının gereksinimleri karşıladığını doğrula
- Düzgün hesap recovery flow’ları kur
Authorization Migration
- Custom authorizer’ları CDK’ya dönüştür
- Düzgün caching stratejileri implement et
- Token validation’ı iyice test et
- Auth endpoint’leri için CORS ayarlarını doğrula
- Mevcut rolleri yeni yapıya map et
IAM Migration
- Mevcut Lambda rollerini audit et
- Least privilege ilkelerini implement et
- Wildcard izinleri kaldır
- Gerektiği yerde resource-based policy’ler ekle
- Cross-account erişim gerekliyse test et
Güvenlik Best Practice’leri
// lib/constructs/security/security-headers.ts
export function addSecurityHeaders(api: IRestApi) {
const responseParameters = {
'method.response.header.X-Content-Type-Options': "'nosniff'",
'method.response.header.X-Frame-Options': "'DENY'",
'method.response.header.X-XSS-Protection': "'1; mode=block'",
'method.response.header.Strict-Transport-Security':
"'max-age=31536000; includeSubDomains'",
'method.response.header.Content-Security-Policy':
"'default-src 'self'",
};
// Tüm method'lara ekle
api.methods.forEach(method => {
method.addMethodResponse({
statusCode: '200',
responseParameters: Object.keys(responseParameters).reduce(
(acc, key) => ({ ...acc, [key]: true }),
{}
),
});
});
}
Security Migration Sonuçları
3 haftalık yoğun güvenlik yeniden inşasından sonra, ölçülebilir iyileştirmeler:
Performans İyileştirmeleri
- Authorization latency: 400ms → 12ms (97% azalma)
- Cache hit oranı: 0% → 94% (JWK caching)
- API response zamanı: Ortalama 1.4s → 0.8s (42% iyileştirme)
- Mobile app algılanan performans: “Yavaş” → “Hızlı” kullanıcı geri bildirimi
Güvenlik Posture
- Over-privileged fonksiyonlar: 47 → 0 (100% eliminasyon)
- Wildcard IAM izinleri: 23 fonksiyon → 0 fonksiyon
- Audit trail kapsamı: 0% → 100% (tüm auth event’leri loglandı)
- Başarısız auth tespiti: Manuel → 30 saniye otomatik alert’ler
- Compliance durumu: Başarısız audit → SOC 2 Type II uyumlu
Operasyonel Verimlilik
- Auth troubleshooting zamanı: Haftada 3 saat → Haftada 15 dakika
- Güvenlik incident’ları: Ayda 2-3 → Ayda 0 (6 aydır devam ediyor)
- Authorization cache hit oranı: 94% (5 dakikalık TTL)
- JWT validation hataları: Günde 15 → Günde 2 (daha iyi error handling)
Business Etkisi
- Enterprise anlaşmaları unblock: 2.3M$ satış pipeline’ı yeniden açıldı
- Compliance audit: SOC 2 Type II geçti
- GDPR ceza riski: 180K (tam compliance)
- Müşteri güveni: Satış demo’larında görünür güvenlik iyileştirmeleri
CDK Versiyon Uyumluluk Notu
Bu uygulama AWS CDK v2.100+ ile test edilmiştir. Cognito özellikleri ve gelişmiş güvenlik özellikleri CDK sürümleri arasında farklılık gösterebilir. Özellikle Cognito gelişmiş tehdit koruması yapılandırması için güncel CDK dokümantasyonunu kontrol edin.
Zor Yoldan Öğrenilen Güvenlik Dersleri
1. Her Zaman Least Privilege ile Başla
Önce: "Action": "*" çünkü “ship etmek daha hızlı”
Sonra: Her fonksiyon, her kaynak için explicit izinler
Etki: Attack surface’de 94% azalma
2. Performans ve Güvenlik Karşılıklı Özel Değil
Önce: “Güvenlik latency ekler” Sonra: Düzgün caching auth’u hem daha hızlı HEM daha güvenli yaptı Etki: Daha güçlü güvenlikle 97% latency azalması
3. Audit Trail Pazarlık Edilemez
Önce: Kimin neye eriştiğine dair sıfır görünürlük Sonra: Tam context ile her auth kararı loglandı Etki: SOC 2 audit’ini geçti, compliance sağladı
4. Her Şeyi (Güvenli Şekilde) Cache’le
Önce: Her request’te JWK fetch Sonra: Invalidation ile multi-level caching Etki: 94% cache hit oranı, 20ms altında auth
5. Role-Based Access Control Ölçeklenebilir
Önce: Fonksiyon başına ad-hoc izinler Sonra: Net sorumlulukları olan standartlaştırılmış roller Etki: Basitleştirilmiş yönetim, daha iyi güvenlik
Sırada Ne Var
Serverless uygulamanız artık gerçekten performans gösteren enterprise-grade authentication ve authorization’a sahip. User management sağlam, API’lar optimize edilmiş JWT verification ile korunmuş ve IAM policy’leri sıkı least privilege ilkelerini takip ediyor.
6. Bölüm’da, tüm migration’ı bir araya getireceğiz:
- Tam migration stratejileri ve zaman çizelgeleri
- Production’da gerçekten işe yarayan test yaklaşımları
- Kariyerinizi kurtaran rollback prosedürleri
- Tüm stack boyunca performans optimizasyonu
- Incident’ları önleyen monitoring ve observability
Güvenlik temeli sağlam. Bu migration’ı düzgün bir şekilde bitirelim.
Serverless Framework'ten AWS CDK'ya Geçiş Rehberi
Serverless Framework'ten AWS CDK'ya tam geçiş sürecini kapsayan 6 bölümlük kapsamlı rehber. Kurulum, uygulama pattern'leri ve best practice'ler dahil.
Serideki tüm yazılar
İlgili yazılar
AWS Cognito ve Verified Permissions ile SaaS yetkilendirme mimarisi. Cedar politika dili, çok kiracılı desenler, JWT token akışı, maliyet analizi ve TypeScript örnekleriyle yaygın hatalar.
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.
Farklı sektörlerde auth sistemleri geliştirdikten sonra öğrendim ki tek boyutlu kimlik doğrulama bir efsane. Her iş alanının kendine özgü gereksinimleri var ve bu da auth mimarinizi dramatik şekilde etkiliyor.
AWS Bedrock + Knowledge Bases + OpenSearch Serverless üstüne CDK ile TypeScript kullanarak RAG agent kurmak — mimari, IAM bağlantısı, otomatik ingestion ve chat UI.
AWS Verified Permissions, SpiceDB, OpenFGA, Cerbos ve OPA dahil harici yetkilendirme platformlarının tarafsız değerlendirmesi. Mimari desenler, maliyet analizi ve mühendislik ekipleri için karar çerçevesi.