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.
- Cloudflare IPv4 aralıkları: cloudflare.com/ips-v4
- Cloudflare IPv6 aralıkları: cloudflare.com/ips-v6
- Bu listeler değişebilir; firewall kuralları Cloudflare IP listesine göre dinamik tutulmalıdır.
CLAUDE CODE (AGENT) İÇİN KESİN UYGULAMA GÖREVLERݶ
-
İptal Edilenler: Hiçbir şifreleme (Bcrypt/Argon2), login/register gRPC servisi veya e-posta ile kod (OTP) gönderme mantığı YAZILMAYACAKTIR.
-
ConnectRPC Interceptor: Yönetim paneli (Admin) servislerine gelen tüm istekleri karşılayacak bir ConnectRPC interceptor'ı (
connect.Interceptor/connect.UnaryInterceptorFunc) yazılacaktır. -
Header Kontrolü: Interceptor, gelen istekteki
CF-Access-JWT-Assertionbaşlığını ConnectRPC request header'ından (req.Header().Get(...)) okuyacaktır. -
JWT Doğrulama Sırası (bu sıraya kesinlikle uyulacaktır):
- Adım 1 — Algoritma whitelist: Token header'ındaki
algalanı kontrol edilir. YalnızcaRS256kabul edilir. Başka bir değer gelirse (HS256, none vb.) istek reddedilir. Cloudflare Access yalnızca RS256 ile imzalar (resmi kaynak: validating-json). - Adım 2 —
issdoğrulama:issclaim'ihttps://<team-name>.cloudflareaccess.comile 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'iniissolarak kullanır. - Adım 3 — JWKS ile imza doğrulama: Token'daki
kiddeğeri,https://<team-name>.cloudflareaccess.com/cdn-cgi/access/certsadresinden alınan JWKS listesindeki sertifikalarla eşleştirilir.public_certdeğil,public_certslistesi kullanılır;kideşleştirmesi zorunludur. - Adım 4 —
expkontrolü: Token süresi geçmişse reddedilir. -
Adım 5 —
auddoğrulama:audclaim'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. -
JWKS Önbellekleme Kuralları:
- Cloudflare anahtarları 6 haftada bir rotate eder; eski anahtar rotasyondan sonra 7 gün daha geçerli kalır.
- Cache implementasyonunda
kiddeğeri eşleştirmesi ile doğru anahtar seçilir. - Önbellek TTL'i makul tutulmalı; bilinmeyen
kidgeldiğinde cache yenilenmeli (key rotation sinyali). -
Kullanılacak Go kütüphanesi:
github.com/lestrrat-go/jwx/v3(JWT doğrulama, kid eşleştirmesi, dahilijwk.Cacheile async auto-refresh). v4 yerine v3 seçildi: v4, Go'nun deneyselencoding/json/v2paketini (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. -
Context Enjeksiyonu: Tüm doğrulamalar geçerliyse, payload içindeki
emailclaim'i Gocontext'ine enjekte edilecektir. Herhangi bir adım başarısız olursa doğrudanconnect.CodeUnauthenticateddönülecektir. -
Rol Tabanlı Yetkilendirme (RBAC): CF Access "bu kişi giriş yapabilir" der, backend "ne yapabilir" der. İki rol tanımlanacaktır:
superadmin— tam yetki (kullanıcı yönetimi, üniversite yönetimi, moderasyon, analitik)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
- Admin Aksiyon Logu (Audit Log): Her başarılı admin RPC çağrısında
aşağıdakiler
admin_audit_logstablosuna kaydedilir: admin_email— işlemi yapan adminadmin_role— o anki rolüaction— yapılan işlem (örn.DeletePost,BanUser)target_id— etkilenen kayıt UUID'sitarget_type— kayıt türü (örn.post,user,comment)ip_address— isteğin kaynağı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>"
}
emailclaim'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 |