API Erişim Kontrolü: Rol Tabanlı Yetkilendirme (RBAC) Rehberi

Üretim ortamında bir API’yi açığa çıkardığınızda, “kim ne yapabilir” sorusunun cevabını net olarak vermezseniz, er ya da geç ciddi bir güvenlik sorunuyla karşılaşırsınız. Rol tabanlı yetkilendirme (RBAC – Role Based Access Control), bu sorunun en pratik ve ölçeklenebilir çözümlerinden biridir. Bu yazıda hem kavramsal temeli hem de gerçek dünya senaryolarında nasıl uygulayacağınızı detaylıca ele alacağız.

RBAC Nedir ve Neden Önemlidir

Klasik yaklaşımda her kullanıcıya tek tek izin atarsınız. Beş kullanıcınız varken bu yönetilebilir, ama elli kullanıcı ve yirmi endpoint olduğunda işler çığırından çıkar. RBAC bu karmaşıklığı çözmek için rolleri ara katman olarak kullanır.

Temel mantık şöyle çalışır: Kullanıcılara doğrudan izin vermek yerine, önce roller tanımlarsınız. Sonra bu rollere izinler atarsınız. Son olarak kullanıcıları rollere dahil edersiniz. Bir kullanıcı birden fazla role sahip olabilir, bir rol de birden fazla izni barındırabilir.

Bir e-ticaret API’sini düşünelim. Müşteri servisi çalışanı sipariş bilgilerini okuyabilmeli ama fiyatları değiştirememeli. Muhasebe ekibi fatura verilerine erişebilmeli ama kullanıcı hesaplarını düzenleyememeli. Bu ayrımı kullanıcı bazında yapmak yerine roller üzerinden yapmak, hem daha temiz hem de daha güvenlidir.

JWT ile Rol Tabanlı Yetkilendirme

Modern REST API’lerinde RBAC genellikle JWT (JSON Web Token) üzerine inşa edilir. Token içine roller yerleştirilir ve her istekte bu roller kontrol edilir.

Token Yapısı

Bir JWT payload’ı şöyle görünebilir:

# JWT payload decode örneği
echo "eyJzdWIiOiJ1c2VyMTIzIiwicm9sZXMiOlsiYWRtaW4iLCJlZGl0b3IiXX0=" | base64 -d
# Çıktı: {"sub":"user123","roles":["admin","editor"]}

Token oluşturmak için basit bir Node.js örneği:

# Node.js ile JWT token oluşturma
cat << 'EOF' > generate_token.js
const jwt = require('jsonwebtoken');

const payload = {
  sub: 'user123',
  email: '[email protected]',
  roles: ['editor', 'viewer'],
  permissions: ['posts:read', 'posts:write', 'users:read'],
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 saat
};

const token = jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256' });
console.log(token);
EOF

JWT_SECRET="supersecretkey123" node generate_token.js

Bu yaklaşımda roller token içine yazılır ve sunucu her istek geldiğinde veritabanına gitmeden rolleri doğrulayabilir. Ancak burada dikkat edilmesi gereken önemli bir nokta var: Token geçerliyken bir kullanıcının rolünü değiştirirseniz, eski token süresi dolana kadar eski rollere sahip olmaya devam eder. Bu yüzden token süresini kısa tutmak kritik önem taşır.

Nginx ile API Gateway Seviyesinde Rol Kontrolü

Uygulama koduna hiç dokunmadan Nginx seviyesinde temel rol kontrolü yapabilirsiniz. Bu özellikle mikroservis mimarilerinde çok işe yarar.

# /etc/nginx/conf.d/api_rbac.conf
server {
    listen 443 ssl;
    server_name api.sirket.com;

    # JWT doğrulama için auth_request kullanımı
    location /api/admin/ {
        auth_request /auth/validate;
        auth_request_set $user_role $upstream_http_x_user_role;
        
        # Sadece admin rolü geçsin
        if ($user_role != "admin") {
            return 403 '{"error":"Yetersiz yetki","required_role":"admin"}';
        }
        
        proxy_pass http://backend_admin;
        proxy_set_header X-User-Role $user_role;
    }

    location /api/reports/ {
        auth_request /auth/validate;
        auth_request_set $user_role $upstream_http_x_user_role;
        
        # admin veya analyst rolü geçsin
        if ($user_role !~ "^(admin|analyst)$") {
            return 403 '{"error":"Yetersiz yetki"}';
        }
        
        proxy_pass http://backend_reports;
    }

    # Auth servisine yönlendirme
    location = /auth/validate {
        internal;
        proxy_pass http://auth_service/validate;
        proxy_pass_request_body off;
        proxy_set_header Content-Length "";
        proxy_set_header X-Original-URI $request_uri;
    }
}

Python/FastAPI ile Granüler Yetkilendirme

Gerçek projelerimde en çok FastAPI kullandığım için bu framework üzerinden detaylı bir örnek verelim.

# FastAPI RBAC middleware kurulumu
pip install fastapi python-jose[cryptography] python-multipart

cat << 'EOF' > rbac_middleware.py
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from functools import wraps
from typing import List, Optional

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

# Rol hiyerarşisi tanımlaması
ROLE_HIERARCHY = {
    "superadmin": ["admin", "editor", "viewer", "analyst"],
    "admin": ["editor", "viewer", "analyst"],
    "editor": ["viewer"],
    "analyst": ["viewer"],
    "viewer": []
}

# İzin matrisi
PERMISSIONS = {
    "users:read":   ["admin", "superadmin"],
    "users:write":  ["admin", "superadmin"],
    "users:delete": ["superadmin"],
    "posts:read":   ["viewer", "editor", "admin", "superadmin", "analyst"],
    "posts:write":  ["editor", "admin", "superadmin"],
    "posts:delete": ["admin", "superadmin"],
    "reports:read": ["analyst", "admin", "superadmin"],
    "reports:export": ["admin", "superadmin"]
}

security = HTTPBearer()
app = FastAPI()

def get_effective_roles(role: str) -> List[str]:
    """Rol hiyerarşisine göre efektif rolleri döner"""
    effective = {role}
    inherited = ROLE_HIERARCHY.get(role, [])
    for r in inherited:
        effective.add(r)
        effective.update(get_effective_roles(r))
    return list(effective)

def require_permission(permission: str):
    """Permission decorator factory"""
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # credentials kwargs içinden alınır
            credentials = kwargs.get('credentials')
            if not credentials:
                raise HTTPException(status_code=401)
            
            try:
                payload = jwt.decode(
                    credentials.credentials, 
                    SECRET_KEY, 
                    algorithms=[ALGORITHM]
                )
                user_role = payload.get("role", "viewer")
                effective_roles = get_effective_roles(user_role)
                allowed_roles = PERMISSIONS.get(permission, [])
                
                if not any(r in allowed_roles for r in effective_roles):
                    raise HTTPException(
                        status_code=status.HTTP_403_FORBIDDEN,
                        detail=f"Bu işlem için '{permission}' izni gerekli"
                    )
            except JWTError:
                raise HTTPException(status_code=401, detail="Geçersiz token")
            
            return await func(*args, **kwargs)
        return wrapper
    return decorator

@app.get("/api/users")
@require_permission("users:read")
async def get_users(credentials: HTTPAuthorizationCredentials = Depends(security)):
    return {"users": ["ali", "veli", "ayse"]}

@app.delete("/api/users/{user_id}")
@require_permission("users:delete")
async def delete_user(
    user_id: str,
    credentials: HTTPAuthorizationCredentials = Depends(security)
):
    return {"message": f"Kullanıcı {user_id} silindi"}
EOF

Bu yapının güzel yanı, yeni bir endpoint eklerken sadece hangi izin gerektiğini belirtmeniz yeterli. Rol mantığını her seferinde yeniden yazmanıza gerek yok.

Veritabanı Tarafında RBAC Modeli

Üretim sistemlerinde roller genellikle veritabanında yönetilir. PostgreSQL üzerinde tipik bir RBAC şeması şöyle görünür:

# PostgreSQL RBAC şema oluşturma
psql -U postgres -d apidb << 'EOF'

-- Roller tablosu
CREATE TABLE roles (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,
    description TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);

-- İzinler tablosu
CREATE TABLE permissions (
    id SERIAL PRIMARY KEY,
    resource VARCHAR(100) NOT NULL,  -- örn: users, posts, reports
    action VARCHAR(50) NOT NULL,     -- örn: read, write, delete
    description TEXT,
    UNIQUE(resource, action)
);

-- Rol-izin ilişkisi
CREATE TABLE role_permissions (
    role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE,
    permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE,
    PRIMARY KEY (role_id, permission_id)
);

-- Kullanıcı-rol ilişkisi
CREATE TABLE user_roles (
    user_id INTEGER NOT NULL,
    role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE,
    assigned_at TIMESTAMP DEFAULT NOW(),
    assigned_by INTEGER,
    expires_at TIMESTAMP,  -- geçici rol atamaları için
    PRIMARY KEY (user_id, role_id)
);

-- Varsayılan rolleri ekle
INSERT INTO roles (name, description) VALUES
    ('superadmin', 'Tüm sistem yetkilerine sahip'),
    ('admin', 'Kullanıcı yönetimi ve içerik yönetimi'),
    ('editor', 'İçerik oluşturma ve düzenleme'),
    ('analyst', 'Raporlara salt okunur erişim'),
    ('viewer', 'Temel içerik görüntüleme');

-- Kullanıcının tüm efektif izinlerini getiren view
CREATE VIEW user_effective_permissions AS
SELECT 
    ur.user_id,
    r.name as role_name,
    p.resource,
    p.action,
    ur.expires_at
FROM user_roles ur
JOIN roles r ON ur.role_id = r.id
JOIN role_permissions rp ON r.id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE (ur.expires_at IS NULL OR ur.expires_at > NOW());

EOF

expires_at alanı özellikle önemli. Geçici yetki vermek istediğinizde, örneğin bir dış danışmanın iki haftalığına sisteme erişmesi gerektiğinde, bu alan sayesinde süre dolduğunda yetki otomatik olarak düşer.

Rate Limiting ile RBAC Entegrasyonu

Sadece “ne yapabilir” değil, “ne kadar yapabilir” sorusu da önemli. Redis üzerinde rol bazlı rate limiting şöyle uygulanabilir:

# Redis tabanlı rol bazlı rate limiting (Python)
cat << 'EOF' > rate_limiter.py
import redis
import time
from typing import Optional

r = redis.Redis(host='localhost', port=6379, db=0)

# Rol bazlı rate limit konfigürasyonu
RATE_LIMITS = {
    "superadmin": {"requests": 10000, "window": 3600},  # saatte 10000
    "admin":      {"requests": 5000,  "window": 3600},  # saatte 5000
    "editor":     {"requests": 1000,  "window": 3600},  # saatte 1000
    "analyst":    {"requests": 500,   "window": 3600},  # saatte 500
    "viewer":     {"requests": 100,   "window": 3600},  # saatte 100
    "api_key":    {"requests": 2000,  "window": 3600},  # API key kullanımı
}

def check_rate_limit(user_id: str, role: str, endpoint: str) -> dict:
    """
    Kullanıcının rate limit durumunu kontrol eder.
    Returns: {"allowed": bool, "remaining": int, "reset_in": int}
    """
    limit_config = RATE_LIMITS.get(role, RATE_LIMITS["viewer"])
    key = f"ratelimit:{role}:{user_id}:{endpoint}"
    current_time = int(time.time())
    window = limit_config["window"]
    max_requests = limit_config["requests"]
    
    pipe = r.pipeline()
    
    # Sliding window implementasyonu
    pipe.zremrangebyscore(key, 0, current_time - window)
    pipe.zcard(key)
    pipe.zadd(key, {str(current_time): current_time})
    pipe.expire(key, window)
    
    results = pipe.execute()
    current_count = results[1]
    
    if current_count >= max_requests:
        # En eski isteğin ne zaman window dışına çıkacağını hesapla
        oldest = r.zrange(key, 0, 0, withscores=True)
        reset_in = int(oldest[0][1]) + window - current_time if oldest else window
        
        return {
            "allowed": False,
            "remaining": 0,
            "reset_in": reset_in,
            "limit": max_requests
        }
    
    return {
        "allowed": True,
        "remaining": max_requests - current_count - 1,
        "reset_in": window,
        "limit": max_requests
    }

# Test
result = check_rate_limit("user123", "editor", "/api/posts")
print(f"İstek durumu: {result}")
EOF

python3 rate_limiter.py

API Key ile Servis Hesabı Yetkilendirmesi

Servisler arası iletişimde JWT yerine API key kullanmak daha yaygındır. Ancak API key’lere de RBAC uygulamak mümkün ve gereklidir.

# API Key yönetim scripti
cat << 'EOF' > api_key_manager.sh
#!/bin/bash

# Yeni API key oluştur ve veritabanına kaydet
generate_api_key() {
    local service_name=$1
    local role=$2
    local expires_days=${3:-365}  # varsayılan 1 yıl
    
    # Güvenli random key oluştur
    API_KEY="ak_$(openssl rand -hex 32)"
    KEY_HASH=$(echo -n "$API_KEY" | sha256sum | cut -d' ' -f1)
    EXPIRES_AT=$(date -d "+${expires_days} days" '+%Y-%m-%d %H:%M:%S')
    
    # PostgreSQL'e kaydet
    psql -U apiuser -d apidb << SQLEOF
    INSERT INTO api_keys (key_hash, service_name, role, expires_at, created_at)
    VALUES ('$KEY_HASH', '$service_name', '$role', '$EXPIRES_AT', NOW())
    ON CONFLICT (service_name) DO UPDATE 
    SET key_hash = '$KEY_HASH', expires_at = '$EXPIRES_AT';
SQLEOF
    
    echo "Servis: $service_name"
    echo "Rol: $role"
    echo "API Key: $API_KEY"
    echo "Son kullanma: $EXPIRES_AT"
    echo "UYARI: Bu key bir daha gösterilmeyecek, kaydedin!"
}

# Kullanım
# generate_api_key "data-pipeline-service" "analyst" 90
# generate_api_key "mobile-backend" "editor" 365
# generate_api_key "monitoring-service" "viewer" 30

generate_api_key "$1" "$2" "$3"
EOF

chmod +x api_key_manager.sh

Audit Log: Kim Ne Yaptı

RBAC’ın önemli bir parçası da erişim denetim kaydıdır. Kim hangi kaynağa ne zaman erişti, hangi işlemi gerçekleştirdi bilgisini tutmak hem güvenlik hem de uyumluluk açısından kritiktir.

# Nginx access log'unu RBAC bilgisiyle zenginleştirme
# /etc/nginx/nginx.conf içine eklenecek log formatı

cat << 'EOF' >> /etc/nginx/nginx.conf

log_format rbac_audit '$remote_addr - $remote_user [$time_local] '
                      '"$request" $status $body_bytes_sent '
                      '"$http_referer" "$http_user_agent" '
                      'role="$http_x_user_role" '
                      'user_id="$http_x_user_id" '
                      'request_id="$request_id" '
                      'response_time="$request_time"';

access_log /var/log/nginx/api_audit.log rbac_audit;
EOF

# Log'ları analiz etmek için basit script
cat << 'EOF' > analyze_audit_log.sh
#!/bin/bash

LOG_FILE="/var/log/nginx/api_audit.log"

echo "=== Son 1 saatteki Admin İşlemleri ==="
grep 'role="admin"' $LOG_FILE | 
    awk -v date="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" '$4 >= "["date' | 
    grep -E '"(POST|PUT|DELETE)' | 
    awk '{print $4, $7, $8, $9}' | head -20

echo ""
echo "=== Başarısız Yetkilendirme Denemeleri (403) ==="
grep ' 403 ' $LOG_FILE | 
    awk '{print $1, $4, $7}' | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== Rol Bazlı İstek Dağılımı ==="
grep -oP 'role="K[^"]+' $LOG_FILE | sort | uniq -c | sort -rn
EOF

chmod +x analyze_audit_log.sh

Yaygın Hatalar ve Nasıl Kaçınılır

Üretim sistemlerinde RBAC uygularken sıkça karşılaşılan hatalar ve bunlardan kaçınma yöntemleri:

Token içindeki rollere tamamen güvenmek: Kullanıcının token’ını değiştirip farklı bir rol iddia etmesi mümkün olmasa da (imza doğrulama bunu engeller), kritik işlemler için token’daki rolü veritabanıyla çapraz doğrulamak daha güvenlidir. Token imzalama anahtarınızı çalan biri istediği rolü yazabilir.

Çok kaba taneli roller: “admin her şeyi yapabilir” yaklaşımı başlangıçta kolay görünür ama tehlikelidir. Admin hesabı ele geçirildiğinde tüm sistem tehlikeye girer. Rolleri işlev bazında bölmek, prensip olarak en az ayrıcalık ilkesini hayata geçirir.

Rol değişikliklerinin geç yansıması: Kullanıcının rolünü düşürdüğünüzde, elindeki aktif token süresi dolana kadar eski yetkilerle işlem yapmaya devam edebilir. Bu durumu çözmek için token kara listesi tutabilirsiniz ya da token süresini kısa tutabilirsiniz.

Yatay yetki yükseltme: Aynı roldeki kullanıcıların birbirinin verilerine erişmesini engellemek sadece RBAC ile değil, kaynak bazlı erişim kontrolü (ABAC) ile de yapılmalıdır. “Bu kullanıcı editör mü?” sorusunun yanında “Bu editör bu içeriğin sahibi mi?” sorusunu da sormalısınız.

Test edilmemiş izin matrisi: RBAC konfigürasyonunu otomatik test etmek şarttır. Hangi rolün neye erişebileceğini, neye erişemeyeceğini test eden bir suite olmadan, bir güncellemeyle yanlışlıkla yetki açabilirsiniz.

Kubernetes Ortamında RBAC

Eğer API’nizi Kubernetes üzerinde çalıştırıyorsanız, Kubernetes’in kendi RBAC sistemiyle API RBAC’ınızı entegre etmeniz gerekmez ama Kubernetes servis hesaplarını API erişimi için kullanıyorsanız dikkatli olun:

# Kubernetes'te API erişimi için servis hesabı oluşturma
kubectl create serviceaccount api-reader -n production

# Role oluştur
kubectl create role api-reader-role 
  --verb=get,list 
  --resource=configmaps,secrets 
  -n production

# Rol bağlama
kubectl create rolebinding api-reader-binding 
  --role=api-reader-role 
  --serviceaccount=production:api-reader 
  -n production

# Servis hesabı token'ını al (Kubernetes 1.24+ için)
kubectl create token api-reader -n production --duration=24h

# Bu token'ı API gateway'inizde kullanarak
# Kubernetes servislerine RBAC kontrollü erişim sağlayabilirsiniz

Sonuç

RBAC uygulamak, “admin/user” ikili ayrımından çok daha fazlasını gerektirir. Rollerin granüler tasarımı, hiyerarşi yönetimi, token stratejisi, rate limiting entegrasyonu ve audit logging, sağlıklı bir API güvenlik katmanının olmazsa olmaz bileşenleridir.

Pratik tavsiye olarak şunu söyleyebilirim: Küçük projede bile başından doğru RBAC kurgusu yapın. “Şimdilik herkes admin olsun, sonra düzeltiriz” yaklaşımı teknik borç olarak geri döner ve o borcu ödemek başlangıçta kurmaktan çok daha pahalıya mal olur. Rollerinizi iş gereksinimlerine göre tasarlayın, teknik kolaylıklara göre değil. “Şu iki birimi ayrı roller mi olmalı?” sorusunu her zaman iş birimlerine sorun.

Son olarak, RBAC’ı bir kere kurup unutulacak bir şey olarak değil, sürekli bakım gerektiren canlı bir sistem olarak görün. Düzenli olarak hangi rollerin hangi izinlere sahip olduğunu gözden geçirin, kullanılmayan rolleri temizleyin ve her çeyrek dönemde bir yetki matrisi audit’i yapın. Güvenlik sadece teknik değil, aynı zamanda bir süreç meselesidir.

Bir yanıt yazın

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