Ana içeriğe geç

Cloudflare Zero Trust + RBAC + Audit Log

SİSTEM BAĞLAMI VE MİMARİ KARAR: Bu projede 2026 endüstri standardı olan "Zero Trust Network Access (ZTNA)" mimarisi uygulanacaktır. Veritabanında şifre, hash, OTP veya TOTP kodu KESİNLİKLE tutulmayacaktır. Local authentication (kendi içimizde giriş/kayıt sistemi) iptal edilmiştir. Tüm ağ erişim ve kimlik kontrolü Cloudflare Zero Trust (Access) hizmetine devredilmiştir.


KRİTİK MİMARİ UYARI: gRPC + Cloudflare Access Uyumsuzluğu

Resmi Kaynak: developers.cloudflare.com/network/grpc-connections

Cloudflare Access, native gRPC (HTTP/2) trafiğini proxy'lemez. Resmi dökümantasyon şunu açıkça belirtir:

"gRPC traffic will be ignored by Access if gRPC is enabled in Cloudflare."

Bu nedenle native gRPC kullanıldığında CF-Access-JWT-Assertion header'ı Go backend'e iletilmez.

Seçilen Çözüm: Flutter Web, tarayıcı kısıtlamaları nedeniyle zaten native gRPC (HTTP/2 bidirectional) kullanamaz; gRPC-Web protokolü kullanmak zorundadır. gRPC-Web, HTTP semantiği üzerinden çalıştığı için Cloudflare Access ile uyumludur ve CF-Access-JWT-Assertion header'ı Go backend'e doğru şekilde iletilir.

Uygulama Kuralı: Go backend connectrpc.com/connect (ConnectRPC) kullanılacaktır. ConnectRPC, gRPC-Web protokolünü doğrudan ve natively destekler; ayrı bir wrapper gerektirmez. Flutter Web tarafında grpc paketi (gRPC-Web için package:grpc/grpc_web.dart) kullanılacaktır. Native gRPC (pure HTTP/2) backend'e hiçbir zaman doğrudan dış erişim olmayacaktır.


GELİŞTİRME STRATEJİSİ (EVRİMSEL MİMARİ)

  • Adım 1 (Şu an kodlanacak): Flutter Web paneli ve Go backend dış internete kapatılıp Cloudflare Access arkasına alınacaktır. Go backend gRPC-Web protokolünü kullanacak, her istekteki Cloudflare onaylı JWT'yi kendi interceptor'ında doğrulayacaktır. Kimlik sağlayıcı (IdP) olarak Google Workspace kullanılacaktır. One-time PIN yedek olarak aktif bırakılacaktır.
  • Adım 2 (Gelecekte - Kod yazılmayacak): Donanım güvenliği (FIDO2/YubiKey) zorunlu hale getirildiğinde, kod tabanına dokunulmadan Cloudflare dashboard üzerinden cihaz doğrulama kuralları eklenecektir.

ORIGIN SUNUCU GÜVENLİĞİ (FIREWALL ZORUNLULUĞU)

Resmi Kaynak: developers.cloudflare.com/fundamentals/security/protect-your-origin-server

Go backend'in IP adresi keşfedilirse saldırgan Cloudflare Access'i tamamen atlayarak doğrudan backend'e ulaşabilir. Bu riski engellemek için:

Uygulama Kuralı: Go backend'in çalıştığı sunucunun firewall'u yalnızca Cloudflare IP aralıklarından gelen trafiği kabul edecek şekilde yapılandırılacaktır.


CLAUDE CODE (AGENT) İÇİN KESİN UYGULAMA GÖREVLERİ

  1. İptal Edilenler: Hiçbir şifreleme (Bcrypt/Argon2), login/register gRPC servisi veya e-posta ile kod (OTP) gönderme mantığı YAZILMAYACAKTIR.

  2. ConnectRPC Interceptor: Yönetim paneli (Admin) servislerine gelen tüm istekleri karşılayacak bir ConnectRPC interceptor'ı (connect.Interceptor / connect.UnaryInterceptorFunc) yazılacaktır.

  3. Header Kontrolü: Interceptor, gelen istekteki CF-Access-JWT-Assertion başlığını ConnectRPC request header'ından (req.Header().Get(...)) okuyacaktır.

  4. JWT Doğrulama Sırası (bu sıraya kesinlikle uyulacaktır):

  5. Adım 1 — Algoritma whitelist: Token header'ındaki alg alanı kontrol edilir. Yalnızca RS256 kabul edilir. Başka bir değer gelirse (HS256, none vb.) istek reddedilir. Cloudflare Access yalnızca RS256 ile imzalar (resmi kaynak: validating-json).
  6. Adım 2 — iss doğrulama: iss claim'i https://<team-name>.cloudflareaccess.com ile tam eşleşmelidir. Bu kontrol JWKS fetch'ten önce yapılır (cache poisoning saldırısını önlemek için). Gelecekte farklı bir IdP (ör. Okta) eklense bile bu değer değişmez — Cloudflare Access her zaman kendi team domain'ini iss olarak kullanır.
  7. Adım 3 — JWKS ile imza doğrulama: Token'daki kid değeri, https://<team-name>.cloudflareaccess.com/cdn-cgi/access/certs adresinden alınan JWKS listesindeki sertifikalarla eşleştirilir. public_cert değil, public_certs listesi kullanılır; kid eşleştirmesi zorunludur.
  8. Adım 4 — exp kontrolü: Token süresi geçmişse reddedilir.
  9. Adım 5 — aud doğrulama: aud claim'i, Cloudflare dashboard → Zero Trust → Access → Applications → "Application Audience (AUD) Tag" bölümünden alınan UUID ile tam eşleşmelidir. Bu kontrol zorunludur; aksi halde başka bir CF Access uygulamasına ait geçerli JWT bu backend'e karşı kullanılabilir.

  10. JWKS Önbellekleme Kuralları:

  11. Cloudflare anahtarları 6 haftada bir rotate eder; eski anahtar rotasyondan sonra 7 gün daha geçerli kalır.
  12. Cache implementasyonunda kid değeri eşleştirmesi ile doğru anahtar seçilir.
  13. Önbellek TTL'i makul tutulmalı; bilinmeyen kid geldiğinde cache yenilenmeli (key rotation sinyali).
  14. Kullanılacak Go kütüphanesi: github.com/lestrrat-go/jwx/v3 (JWT doğrulama, kid eşleştirmesi, dahili jwk.Cache ile async auto-refresh). v4 yerine v3 seçildi: v4, Go'nun deneysel encoding/json/v2 paketini (GOEXPERIMENT=jsonv2) zorunlu kılıyor ve JWKS cache'i ayrı bir companion pakete taşımış; v3 ise bu bayrağı gerektirmez, JWKS cache'i dahilidir ve kararlıdır.

  15. Context Enjeksiyonu: Tüm doğrulamalar geçerliyse, payload içindeki email claim'i Go context'ine enjekte edilecektir. Herhangi bir adım başarısız olursa doğrudan connect.CodeUnauthenticated dönülecektir.

  16. Rol Tabanlı Yetkilendirme (RBAC): CF Access "bu kişi giriş yapabilir" der, backend "ne yapabilir" der. İki rol tanımlanacaktır:

  17. superadmin — tam yetki (kullanıcı yönetimi, üniversite yönetimi, moderasyon, analitik)
  18. moderator — yalnızca içerik moderasyonu (post/yorum silme)

Interceptor, JWT doğrulandıktan sonra context'teki email ile admins tablosunu sorgular ve rolü context'e enjekte eder. Her RPC handler gerekli rolü kontrol eder.

Reddedilme durumları: * Email admins tablosunda bulunamazsa → connect.CodePermissionDenied * Email var ama rol RPC için yetersizse → connect.CodePermissionDenied

  1. Admin Aksiyon Logu (Audit Log): Her başarılı admin RPC çağrısında aşağıdakiler admin_audit_logs tablosuna kaydedilir:
  2. admin_email — işlemi yapan admin
  3. admin_role — o anki rolü
  4. action — yapılan işlem (örn. DeletePost, BanUser)
  5. target_id — etkilenen kayıt UUID'si
  6. target_type — kayıt türü (örn. post, user, comment)
  7. ip_address — isteğin kaynağı
  8. created_at — UTC zaman damgası

Bu tablo append-only'dir; kayıt silinemez, güncellenemez. KVKK Madde 12 ve GDPR Madde 5/2 kapsamında zorunludur.


JWT PAYLOAD YAPISI VE TOKEN ÖMRü

Resmi Kaynak: developers.cloudflare.com/.../application-token

Cloudflare Access tarafından üretilen JWT payload yapısı:

{
  "iss": "https://<team-name>.cloudflareaccess.com",
  "aud": ["<application-audience-uuid>"],
  "sub": "<user-unique-id>",
  "email": "user@example.com",
  "exp": 1234567890,
  "iat": 1234567890,
  "nbf": 1234567890,
  "type": "app",
  "identity_nonce": "<value>",
  "country": "<country-code>"
}
  • email claim'i kullanıcı kimlik doğrulamasında her zaman mevcuttur (service token'larında yoktur — service token bu projede kullanılmamaktadır).
  • Varsayılan token TTL: 24 saat (Application session duration; Cloudflare dashboard'dan anlık zaman aşımı ile 1 ay arasında yapılandırılabilir). Admin panel için daha kısa bir TTL (örn. 1-4 saat) önerilir.

VERİTABANI: ADMİN TABLOLARI

admins tablosu

CF Access JWT'sindeki email claim'i bu tabloyla eşleştirilir. Bu tablo "bu email'in rolü nedir?" sorusunu cevaplar.

Alan Tip Açıklama
id UUID PRIMARY KEY
email VARCHAR(255) UNIQUE, CF Access JWT email ile eşleşmeli
role TEXT superadmin veya moderator
created_by VARCHAR(255) Ekleyen adminin email'i
created_at TIMESTAMPTZ
updated_at TIMESTAMPTZ

İlk Admin (Bootstrap): admins tablosu boşken hiç kimse giriş yapamaz. İlk superadmin veritabanı migration'ı (seed) ile eklenecektir. Migration dosyası ortam değişkeninden (INITIAL_SUPERADMIN_EMAIL) okunarak ilk kaydı oluşturur. Bu işlem sadece bir kez, kurulumda çalışır.

admin_audit_logs tablosu

Append-only. Hiçbir kayıt silinemez veya güncellenemez. KVKK/GDPR zorunluluğu.

Alan Tip Açıklama
id UUID PRIMARY KEY
admin_email VARCHAR(255) İşlemi yapan admin
admin_role TEXT O anki rolü
action TEXT RPC adı (örn. DeletePost, BanUser)
target_id UUID Etkilenen kayıt
target_type TEXT post, user, comment, university
ip_address TEXT İstek kaynağı
created_at TIMESTAMPTZ UTC, otomatik