Hasura Permission Sistemi: Rol Tabanlı Erişim Kontrolü

Gerçek dünya projelerinde en çok baş ağrıtan konulardan biri kim neye erişebilir sorusudur. Bir e-ticaret uygulaması düşünün: müşteri kendi siparişlerini görmeli, satıcı sadece kendi ürünlerini yönetmeli, admin her şeye erişebilmeli. Bunu GraphQL API’de elle yazmak saatler alır, test etmek günler. Hasura’nın permission sistemi tam bu noktada devreye giriyor ve bu karmaşıklığı deklaratif bir yapıyla çözüyor.

Hasura Permission Sisteminin Temelleri

Hasura’da erişim kontrolü rol tabanlı çalışır. Her GraphQL isteği bir rol ile gelir ve o rol hangi tablolara, hangi kolonlara, hangi satırlara erişebileceğini belirler. Rol bilgisi JWT token içinde ya da session variable olarak Hasura’ya iletilir.

Hasura’da dört temel permission tipi vardır:

  • Select: Veri okuma yetkisi
  • Insert: Veri ekleme yetkisi
  • Update: Veri güncelleme yetkisi
  • Delete: Veri silme yetkisi

Her permission ayrı ayrı konfigüre edilir. Bir rol insert yapabilir ama update yapamaz, ya da sadece belirli kolonları görebilir. Bu granüler yapı çok güçlü ama dikkatli düşünmek gerekiyor.

Önemli bir kavramı hemen açıklayayım: admin rolü Hasura’da her şeye erişebilen varsayılan roldür ve permission tanımına gerek yoktur. Tüm diğer roller için permission’ları açıkça tanımlamanız gerekir, aksi halde o rol o tabloya hiç erişemez.

Session Variable Nedir, Nasıl Çalışır?

Permission kuralları yazarken en çok kullandığınız araç session variable’lardır. Bunlar JWT claim’leri ya da webhook response’undan gelen key-value çiftleridir.

# JWT token içinde Hasura'nın beklediği claim yapısı
# Bu değerler https://hasura.io/jwt/claims namespace'i altında olmalı

{
  "sub": "1234567890",
  "name": "Ahmet Yılmaz",
  "iat": 1516239022,
  "https://hasura.io/jwt/claims": {
    "x-hasura-default-role": "user",
    "x-hasura-allowed-roles": ["user", "editor"],
    "x-hasura-user-id": "42",
    "x-hasura-org-id": "7"
  }
}

Hasura bu claim’leri alır ve permission kurallarında X-Hasura-User-Id gibi değişkenler olarak kullanmanıza izin verir. Büyük/küçük harf duyarsızdır ama convention olarak büyük harfle yazılır.

# Hasura environment değişkenleri ile JWT secret konfigürasyonu
# docker-compose.yml içinde

environment:
  HASURA_GRAPHQL_JWT_SECRET: '{"type":"RS256","jwk_url":"https://YOUR_AUTH_PROVIDER/.well-known/jwks.json"}'
  HASURA_GRAPHQL_ADMIN_SECRET: "super-secret-admin-key"
  HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "anonymous"

HASURA_GRAPHQL_UNAUTHORIZED_ROLE ayarı önemli: token olmadan gelen istekler bu rol ile değerlendirilir. Public API’lerde anonymous kullanıcılara ne göstereceğinizi buradan kontrol edersiniz.

Row Level Permission: Satır Bazında Erişim Kontrolü

Bu özellik Hasura’yı gerçekten güçlü yapan şey. PostgreSQL’in Row Level Security’sine benzer ama Hasura bunu otomatik olarak GraphQL sorgularına uygular.

Bir blog platformu senaryo olarak düşünelim. Kullanıcılar sadece kendi blog yazılarını görebilmeli, yayınlanmış yazılar ise herkese açık olmalı.

# Hasura CLI ile metadata yönetimi yapıyorsanız
# tables.yaml içinde permission tanımı şöyle görünür

- table:
    schema: public
    name: posts
  select_permissions:
  - role: user
    permission:
      columns:
        - id
        - title
        - content
        - created_at
        - status
      filter:
        _or:
          - author_id:
              _eq: X-Hasura-User-Id
          - status:
              _eq: published
      limit: 50
      allow_aggregations: false

Bu kuralı analiz edelim: user rolündeki biri posts tablosunu sorguladığında Hasura otomatik olarak bir WHERE koşulu ekler. Ya author_id o kullanıcının ID’sine eşit olmalı, ya da status published olmalı. Kullanıcı bu WHERE’i bypass edemez, GraphQL sorgusuna ne yazarsa yazsın.

limit: 50 de önemli bir güvenlik önlemi. Kullanıcı sorguya limit koymasa bile Hasura maximum 50 satır döner. DDoS ya da veri sızdırma girişimlerine karşı iyi bir koruma.

Column Level Permission: Kolon Bazında Erişim

Bazen bir tabloya erişim verilmeli ama hassas kolonlar gizlenmeli. Kullanıcı tablosu bunun klasik örneği.

# Hasura Console'da ya da metadata'da
# users tablosu için müşteri rolü permission

- role: customer
  permission:
    columns:
      - id
      - name
      - email
      - avatar_url
      - created_at
    # password_hash, internal_notes, stripe_customer_id gibi kolonlar YOK
    filter:
      id:
        _eq: X-Hasura-User-Id

Burada dikkat edin: filter sadece kendi profilini görmesini sağlıyor. columns ise hangi alanların görünür olduğunu belirliyor. İki katman halinde güvenlik sağlanmış oluyor.

Şimdi bir admin rolü ekleyelim, admin tüm kullanıcıları görebilmeli ama yine de bazı kolonlar gizli kalmalı:

- role: support_agent
  permission:
    columns:
      - id
      - name
      - email
      - created_at
      - subscription_status
      - last_login_at
    # stripe_customer_id gibi finansal detaylar support_agent'ta da yok
    filter: {}
    # Boş filter = tüm satırlara erişim
    limit: 100
    allow_aggregations: true

Insert Permission ve Column Presets

Insert permission’da özellikle column_presets özelliği çok işe yarar. Kullanıcı veri eklerken bazı alanları otomatik olarak session variable’dan doldurabilirsiniz.

# posts tablosu için insert permission
# Kullanıcı yeni yazı oluştururken author_id'yi kendisi set edemesin

- role: user
  permission:
    columns:
      - title
      - content
      - status
      # author_id burada YOK, kullanıcı bunu set edemez
    check:
      status:
        _in:
          - draft
          - published
    set:
      author_id: X-Hasura-User-Id
      # author_id otomatik olarak JWT'den gelen user ID ile doldurulur

Bu çok kritik bir güvenlik önlemi. Eğer author_id kolonunu insert columns’a eklerseniz, kullanıcı başkası adına yazı oluşturabilir. set ile bu alanı otomatik dolduruyorsunuz ve kullanıcı override edemiyor.

check koşulu da önemli: insert sonrası bu koşul sağlanmıyorsa işlem rollback edilir. Burada status’un sadece draft ya da published olabilmesini zorunlu kıldık.

# Hasura CLI ile permission'ları uygulama
hasura metadata apply --endpoint http://localhost:8080 --admin-secret myadminsecretkey

# Metadata export etme (mevcut konfigürasyonu çekme)
hasura metadata export --endpoint http://localhost:8080 --admin-secret myadminsecretkey

# Permission değişikliklerini test etme
hasura console --endpoint http://localhost:8080 --admin-secret myadminsecretkey

Update Permission ve Pre/Post Check

Update permission’da iki önemli kavram var: filter (hangi satırları güncelleyebilir) ve check (güncelleme sonrası satır nasıl görünmeli).

# E-ticaret senaryosu: Sipariş durumu güncellemesi
# Sadece satıcı kendi siparişlerinin durumunu güncelleyebilir
# Ve sadece belirli geçişlere izin verilmeli

- role: seller
  permission:
    columns:
      - status
      - tracking_number
      - shipped_at
    filter:
      seller_id:
        _eq: X-Hasura-User-Id
    check:
      status:
        _in:
          - processing
          - shipped
          - delivered
      # cancelled gibi hassas durumlar seller tarafından set edilemez

Bu örnekte satıcı sadece kendi siparişlerini güncelleyebilir (filter), ve güncelleme sonrası sipariş durumu belirlenen değerlerden biri olmalıdır (check). İptal etmek için farklı bir süreç gerekir.

Computed Field’lar ile Permission

Bazen permission kuralları basit kolon karşılaştırmasından öte mantık gerektirir. Computed field’lar burada devreye girer.

# PostgreSQL'de computed field fonksiyonu oluşturma
# Kullanıcının premium üye olup olmadığını kontrol eder

CREATE OR REPLACE FUNCTION public.user_is_premium(user_row users)
RETURNS boolean AS $$
  SELECT EXISTS (
    SELECT 1 FROM subscriptions
    WHERE user_id = user_row.id
    AND status = 'active'
    AND expires_at > NOW()
  );
$$ LANGUAGE sql STABLE;
# Bu computed field'ı Hasura'ya ekledikten sonra
# premium içerik tablosu için permission

- role: user
  permission:
    columns:
      - id
      - title
      - preview_content
      # full_content sadece premium üyelere açık
    filter: {}

- role: premium_user
  permission:
    columns:
      - id
      - title
      - preview_content
      - full_content
      - attachments
    filter: {}

Burada farklı roller farklı kolon setleri görüyor. Ama bu yaklaşım yerine tek rol içinde computed field kullanarak da çözebilirsiniz, bu proje yapınıza göre değişir.

Çoklu Tenant Senaryosu

SaaS uygulamalarının en önemli güvenlik gereksinimi tenant izolasyonudur. Tenant A, Tenant B’nin verilerini görmemeli.

# JWT claim'ine org-id eklendi
# Tüm tablolarda org_id kolonu var

# products tablosu için tenant izolasyonu
- role: org_member
  permission:
    columns:
      - id
      - name
      - description
      - price
      - stock
      - category_id
    filter:
      org_id:
        _eq: X-Hasura-Org-Id
    limit: 200

- role: org_admin
  permission:
    columns:
      - id
      - name
      - description
      - price
      - stock
      - category_id
      - cost_price
      - supplier_id
    filter:
      org_id:
        _eq: X-Hasura-Org-Id
    limit: 500
    allow_aggregations: true

Her sorgu otomatik olarak WHERE org_id = [JWT'den gelen org_id] koşulunu içerir. Kullanıcı ne kadar uğraşırsa uğraşsın başka bir organizasyonun verisine erişemez.

Insert ve update için de aynı izolasyonu sağlamalısınız:

# products tablosu insert permission
- role: org_admin
  permission:
    columns:
      - name
      - description
      - price
      - stock
      - category_id
      - cost_price
      - supplier_id
    check:
      org_id:
        _eq: X-Hasura-Org-Id
    set:
      org_id: X-Hasura-Org-Id
    # org_id otomatik set ediliyor, check ile de doğrulanıyor

Permission Debug ve Test Etme

Permission hatalarını debug etmek başta kafa karıştırıcı olabilir. Hasura Console’da bunu kolaylaştıran araçlar var.

# Hasura Console'da belirli bir rol ile test sorgusu çalıştırma
# Headers bölümüne şunları ekleyin:

X-Hasura-Role: user
X-Hasura-User-Id: 42
X-Hasura-Org-Id: 7

# Bu şekilde admin olmadan belirli bir rolün ne göreceğini test edebilirsiniz
# CLI ile permission'ları validate etme
# hasura-cli ile metadata diff alabilirsiniz

hasura metadata diff --endpoint http://localhost:8080 --admin-secret myadminsecretkey

# Belirli bir tablonun permission'larını kontrol etmek için
# metadata/databases/default/tables/public_posts.yaml dosyasına bakın

Sık karşılaşılan hatalar:

  • “field not found in type”: Kolon permission’larda yok ama sorguda isteniyor
  • “not found in selection set”: Relationship için permission tanımlanmamış
  • Boş array dönmesi: Filter koşulu sağlanmıyor, veri var ama permission engeli

İlişkili tablolarda permission’ları unutmak çok yaygın bir hata. Ana tabloya erişim verdim ama join yaptığım tabloya vermedim, sonuç null ya da boş döner.

# GraphQL introspection ile hangi alanların görünür olduğunu kontrol etme
# user rolü ile çalıştırın

query {
  __type(name: "posts") {
    fields {
      name
      type {
        name
      }
    }
  }
}
# Bu sorgu sadece o rolün görebildiği alanları listeler

Hasura Permission ve Performance

Permission filtreleri her sorguya eklendiği için index’ler kritik önem kazanır. Filter’da kullandığınız kolonlarda mutlaka index olmalı.

# Permission'larda sık kullanılan kolonlar için index oluşturma
psql -U postgres -d mydb << 'EOF'

-- user_id bazlı filtreleme için
CREATE INDEX CONCURRENTLY idx_posts_author_id ON posts(author_id);

-- org_id izolasyonu için
CREATE INDEX CONCURRENTLY idx_posts_org_id ON posts(org_id);

-- Composite index: org_id + status kombinasyonu
CREATE INDEX CONCURRENTLY idx_posts_org_status ON posts(org_id, status);

-- Partial index: sadece published yazılar için
CREATE INDEX CONCURRENTLY idx_posts_published ON posts(created_at)
WHERE status = 'published';

EOF

Permission filter’ları WHERE clause’a dönüştüğü için sorgu planı bu index’leri kullanır. EXPLAIN ANALYZE ile kontrol etmeyi alışkanlık haline getirin.

Inheritance ve Role Hiyerarşisi

Hasura’da built-in rol hiyerarşisi yoktur. admin her şeyi görebilir ama kendi tanımladığınız super_admin rolü için tüm permission’ları ayrıca yazmanız gerekir. Bu bazen sıkıcı ama aslında daha güvenli çünkü implicit inheritance güvenlik açıklarına yol açabilir.

Tekrar eden permission’ları azaltmak için Hasura CLI ile template yaklaşımı kullanabilirsiniz:

# Birden fazla tabloda aynı permission pattern'ini uygulamak için
# basit bir bash script

#!/bin/bash

TABLES=("orders" "products" "invoices" "shipments")
ADMIN_SECRET="myadminsecretkey"
ENDPOINT="http://localhost:8080"

for table in "${TABLES[@]}"; do
  echo "Applying permission for table: $table"
  
  curl -s -X POST 
    -H "X-Hasura-Admin-Secret: $ADMIN_SECRET" 
    -H "Content-Type: application/json" 
    -d "{
      "type": "pg_create_select_permission",
      "args": {
        "table": {"name": "$table", "schema": "public"},
        "role": "org_member",
        "permission": {
          "columns": "*",
          "filter": {"org_id": {"_eq": "X-Hasura-Org-Id"}},
          "limit": 100
        }
      }
    }" 
    "$ENDPOINT/v1/metadata" | jq .
    
done

Bu script her tablo için aynı org_member permission’ını uygular. Büyük projelerde zaman kazandırır.

Sonuç

Hasura’nın permission sistemi ilk bakışta basit görünse de gerçek dünya senaryolarında oldukça derin bir yapı sunuyor. Row level security, column filtering, column presets ve computed field’ların kombinasyonu neredeyse her türlü erişim kontrolü senaryosunu karşılıyor.

Pratik önerilerim:

  • Permission tasarımına başlamadan önce kullanıcı rollerinizi ve senaryolarınızı kağıda dökün
  • Her yeni tablo eklediğinizde tüm roller için permission’ları gözden geçirin, unutmak çok kolay
  • Session variable’lar JWT’den geliyorsa auth provider’ınızın doğru claim’leri gönderdiğini test edin
  • Permission filter’larında kullandığınız tüm kolonlarda index olduğundan emin olun
  • Hasura Console’daki rol bazlı test özelliğini agresif şekilde kullanın, canlıya çıkmadan önce her rolü test edin
  • Metadata’yı versiyon kontrolüne alın, permission değişiklikleri kod review sürecinden geçmeli

Permission sistemi doğru kurulduğunda güvenlik endişesi olmadan hızla geliştirme yapabilirsiniz. Yanlış kurulduğunda ise güvenlik açıkları o kadar sessiz olur ki fark etmek zor olabilir. Bu yüzden başlangıçta biraz daha zaman harcamak her zaman değer.

Bir yanıt yazın

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