Serverless Framework'ten AWS CDK'ya Geçiş: Bölüm 5 - CI/CD ve Deployment
CDK uygulamaları için CI/CD pipeline'ları kurma. GitHub Actions, CodePipeline ve deployment stratejileri.
CDK migration'ımızın 9. haftası. Her şey sorunsuz gidiyordu ta ki Güvenlik Müdürümüz sabah standup'ına tek bir soruyla gelene kadar: "Hangi Lambda fonksiyonları customer ödeme verilerine erişebiliyor?"
Yirmi üç yüz ona sessizce baktı. Serverless Framework kurulumumuz 18 ay boyunca organik olarak büyümüştü. Fonksiyonlar "*"
IAM izinlerine sahipti çünkü "ship etmek daha hızlıydı." Authorization logic'i 12 farklı custom authorizer'a dağılmıştı. Kimin neye erişebildiğine dair sıfır audit trail'imiz vardı.
O soru, 47 over-privileged fonksiyon ve 180K dolarlık potansiyel compliance cezası ortaya çıkaran 3 haftalık güvenlik audit'ini tetikledi. Bu, canlı migration sırasında enterprise-grade authentication ve authorization yeniden inşa etme hikayesi - tek bir user session'ını bozmadan.
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 Kabusu Audit'i#
Bir şeyi düzeltmeden önce neye sahip olduğumuzu anlamamız gerekiyordu. Audit authentication cehennemimizi ortaya çıkardı:
Serverless Framework Gerçeklik Kontrolü#
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 "2022'de bir zaman."
Audit Trail: Hiç. Authorization kararlarının sıfır loglama'sı. "Kim neye ne zaman erişti" sorusuna cevap verecek yol yok.
Business Etkisi#
- Compliance riski: Aşırı geniş data erişimi için 180K$ potansiyel GDPR cezası
- 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: 'noreply@yourcompany.com',
replyTo: 'support@yourcompany.com',
},
// 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$ → 0$ (tam compliance)
- Müşteri güveni: Satış demo'larında görünür güvenlik iyileştirmeleri
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 bulletproof, 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.
Yorumlar (0)
Sohbete katıl
Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap
Henüz yorum yok
Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!
Yorumlar (0)
Sohbete katıl
Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap
Henüz yorum yok
Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!