İki Veri Arasında ki Benzerliğin Hesaplanması – Ruby

Bir RoR projesinde, iki farklı veritabanında bulunan ad-soyad ve kimlik numarası verilerinin birbirlerine ne kadar benzediğinin hesaplanması ve farklı veri setlerinin birbiriyle aynı olma olasılığının tespiti gibi bir ihtiyacım olmuştu.

Örneğin aşağıda ki iki veri birbirine ne kadar benziyor, bu kişiler gerçekten aynı kişiler olabilir mi hesaplamam gerekiyordu:

Bu hesaplamayı yapabilmek için Elasticsearch ve Ruby’nin marifetleri yeterli oldu. Elasticsearch’ün Ruby için sağladığı “records.each_with_hit” metodu ile herhangi iki veri birbirlerine ne kadar benziyor (yakınsıyor) kontrol edebilir ve benzerlik oranını 0-2 aralığında matematiksel olarak alabilirsiniz.

Örnek betik şu şekilde:

Betikte bulunan “1.2” değeri, tamamen benim istediğim yakınlık derecesini ifade etmekte. Daha yüksek yakınlığa sahip verileri tespit etmek için bu değeri arttırabilir, daha geniş bir aralık almak için azaltabilirsiniz. Betik çalıştıktan sonra şöyle bir çıktı veriyor:

Kolaylıklar.

Ruby on Rails ve Güvenlik – CSRF ve API Talepleri

CSRF ve Authenticity Token

Rails’te herhangi bir formdan POST request yaparken authenticity_token parametresi de gönderilmekte. Bu parametre CSRF saldırılarına karşı Rails’in kullandığı default savunma mekanizmasının bileşenlerinden biri. Aşağıda POST işlemi yapan örnek bir form bulunuyor, hidden field olarak authenticity_token gönderildiğini görebilirsiniz.

İşin hikayesi ise şöyle; Rails POST request’leri için user’ın session bilgisini temel alarak rastgele bir hash oluşturup bunu form içerisinde hidden field olarak ekliyor ve request esnasında gönderiyor. Oluşturulan token form submit edildiğinde diğer parametrelerle birlikte gönderiliyor ve web uygulaması tarafından işleniyor.

POST request’in akabinde web sunucumuz gönderilen token ile mevcut kullanıcının session bilgisini kıyaslıyor ve eşleşmeyen bir durum bulunduğunda bunu saldırı olarak kabul ediyor. Eğer Rails’in default davranışını değiştirmediyseniz request’in hataya düşmesi gerekiyor. Aşağıda POST işlemi yapan bir form görebilirsiniz. Henüz token’ı kurcalamadık.

POST işlemi yapan form
POST işlemi yapan form

Session’a erişme şansı olmayan bir saldırgan gibi davranıp token’ı değiştirelim ve neler olduğunu inceleyelim (Token’ın son 2 karakteri olan “==” karakterlerini “??” olarak değiştirdim):

Parametreleri değiştirilmiş form.
Parametreleri değiştirilmiş form.

Sonuç olarak beklendiği üzere uygulama exception’a düştü ve hata verdi =>

Rails CSRF protection

Uygulamanın bu davranışı göstermesini sağlayan ise application_controller’da bulunan “protect_from_forgery” metodu.

Kod aslında yaptığı işi açıkça söylüyor. Diğer bir yandan da API’lar için null_session kullanmamızı öneriyor. Eğer uygulamanız içerisinde bir API barındıracaksanız protect_from_forgery with: :exception yaptığınız request’leri hataya düşürecek çünkü Rails authenticity_token’ı sizin request’iniz içerisine eklemeyecek. Deneyelim:

Konfigürasyon: 

API Request:

API Request Sonucu:

with: :exception ile uygulamayı hataya düşürme davranışına alternatif olarak reset_session ve null_session seçeneklerini de kullanabilirsiniz:

  • :reset_session – Oturumu sıfırlar.
  • :null_session – Request esnasında yeni boş bir oturum sağlar ancak mevcut oturumu sıfırlamaz.

Detaylardan bir kaç ilginç nokta:

  1. Yeni bir Rails uygulaması oluşturduğunuzda “protect_from_forgery” metodu with: :exception ayarlı olarak geliyor.
  2. Rails 4’te => “protect_from_forgery” metodunu tek başına çağırdığınızda default davranışı “reset_session” şeklinde. Bknz: source.
  3. Rails 3’te => “protect_from_forgery” metodunu tek başına çağırdığınızda default davranışı “null_session” şeklinde.

GET mi – POST mu?

protect_from_forgery metodu sadece POST metodu için çalışmakta. Diğer bir değişle GET request’i için CSRF koruması uygulanmıyor. Bu durum Rails kaynak kodu içerisinde şu şekilde ifade ediliyor:

“GET requests are not protected since they don’t have side effects like writing to the database and don’t leak sensitive information.”

Sonuç olarak GET request’lerine hassas bilgiler döndürmemek ve GET request’lerinden gelen parametreleri veritabanına yazmamak önem taşıyor. Eğer hatalı bir kurgu izlemiş ve veritabanı ile olan ilişkilerinizi GET üzerine kurmuşsanız öncelikle bu hatalı kurguyu değiştirmeniz gerekiyor.

API Uygulamaları ve CSRF

Web uygulamanız içerisinde API sunma niyetindeyseniz yukarıda bahsettiğim şekilde “with: :exception” API request’lerinizi hataya düşürecektir çünkü browser üzerinden yapılmayan request’lerde Rails authenticity_token parametresini request’e ekleyemeyecek. O halde hem API  request’lerini hemde normal uygulama request’lerini aynı anda nasıl güvende tutacağız?

1) JSON request’lerini protect_from_forgery’den muaf tut

Request =>

Response =>

Response içerisinde CSRF token’ın doğrulanamadığını söylüyor ancak :exception çalıştırmadığımız için ve default davranış “reset_session” olduğu için API requestimiz başarıyla çalışıyor ve yanıtını alıyor. Bu uyarı sadece loglanıyor ve client’ın sorgularına engel olmuyor. Diğer bir yandan form üzerinden POST yaparken token’ı değiştirirsek uygulama session’ı sıfırlayacak ve POST request’imiz iptal edilecek.

Request =>

Response =>

Bir önce ki POST request ile bu POST request arasında fark, ilk request’in /query yoluna yapılmış, ikinci request’in ise /query.json yoluna yapılmış olması. Dolayısıyla request bir JSON isteği olduğu için uygulama artık loglara token doğrulanamadı hatasıda basmıyor.

Dezavantaj => Eğer  :exception kullanmak istiyorsanız /query yolunun default davranışından fedakarlık etmeniz gerekecek çünkü uygulama bu yolda hata verecek.

Sonuç olarak vardığım en sağlıklı sonuç şu konfigürasyon ile oldu =>

2) API controller’larını protect_from_forgery’den muaf tut

Eğer API request’lerini idare eden controller sadece JSON sunuyorsa callback’lerden faydalanabiliriz. Eğer controller hem bir template render ediyor hemde JSON sunuyorsa CSRF korumasını kapatmak güvenlik zaafiyetine yol açacaktır.

Sadece index action’unu muaf tut =>

Controller’ı muaf tut =>

Diğer detaylar için metodun kaynak kodunu okuyabilirsiniz:

https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/request_forgery_protection.rb

 

Ruby on Rails ve Güvenlik – Routes

Rails uygulamalarında route’ların güvenliğini sağlarken Devise gibi authentication GEM’lerinin sağladığı helper’lardan ve lambda metodundan yararlanabilirsiniz. Örneğin sadece super_admin rolüne sahip kullanıcılara açık olmasını istediğiniz route’ları şu şekilde tanımlayabilirsiniz:

Artık admin/resque, admin/ip_lists ve admin/token_users yolları sadece super_admin’ler için erişilebilir olacak.

Model tarafında ise is_super_admin? metodunu şu şekilde tanımlı:

Selamlar.

Passenger ve Nginx Konfigürasyonu

Önceki Yazılar:

Özenle hazırladığım, sorunsuz çalışan nginx konfigürasyonumu belki birileri göz atar diyerek paylaşıyorum. Yurtdışında pek çok sistem yöneticisi bu şekilde konfigürasyon dosyalarını birbirleri ile paylaşarak peer-review yapmaktalar. Bende bu review’ların pek çoğunu ve official nginx dökümanını okuyarak aşağıdaki konfigürasyonu hazırladım.

Konfigürasyon Debian 7.7 kurulu olan bir Rails sunucusu içindir. Web sunucu olarak Nginx + Passenger kullandım. Veritabanı sunucusu ise MySQL 14.14. Uygulama olarak Ruby 2.1.5 sürümü ve Rails 4.1.2 kurulu. Makinada 8 GB RAM bulunuyor ve 8 çekirdekli. Makina üzerinde sadece 1 tane Rails uygulaması koşuyor ve onunda VM boyutu 344mb. passenger-memory-stats çıktısı aşağıdaki şekilde:

passenger-memory-stats

/etc/nginx/nginx.conf dosyası:

Daha sonra:

/etc/nginx/sites-enabled/ klasörü içerisinde ais.conf isimli bir dosya oluşturun – içeriği şöyle olacak:

Nginx konfigürasyonunda hata varmı kontrol için:

Nginx'e Rails Uygulamasının Deploy Edilmesi

Phusion Passenger üzerinde bir Rails (veya herhangi bir Rack uygulaması) deploy edebilmek için uygulamanın bulunduğu dizinde üç dosyanın bulunması zorunludur:

  • config.ru dosyası
  • public/ klasörü
  • tmp/ klasörü

Yani /herhangi/bir/klasor/‘de bulunan Rack uygulamanızın minimum görünümü şu olmalıdır:

Uygulamanız çalışabilmesi için nginx konfigürasyon dosyanızı (/etc/nginx/nginx.conf) yapılandırmanız gereklidir:

Daha sonra nginx ve passenger’ı restart etmelisiniz:

En temel düzeyde bir Rails uygulaması bu şekilde deploy edilebilir. Uygulamanın kurulu için, bulunduğu dizine düşerek, aşağıdaki adımları takip edin.

Bundle et:

Uygulama config dosyasını düzenle:

Dosya izinlerini güncelle:

Uygulamayı kur:

Son olarak:

Hepsi bu kadar.

Nginx ve Passenger Kurulumu – Ubuntu ve Debian

Nginx ve Passenger Kurulumu

Nginx ve passenger kurulumu ile ilgili internette pek çok İngilizce döküman bulunuyor ancak bunların pek çoğu resmi Passenger dökümanında anlatılan yolların dışında yöntemler kullanmışlar. Bunları uygulayıp, ne gibi zorluklara yol açtığını gördüğüm için, Passenger ve Nginx’in tüm dökümanlarını baştan okuyup bu yazıyı hazırladım. Öncelikle sisteminizde Ruby ve Rails kurulu değilse rbenv aracını kullanarak kurulumu yapmanız gerekiyor.

Passenger tarafından Ubuntu ve Debian için official APT reposu sağlanmakta. Bu repoyu kullandığınızda hem kurulumu kolayca yapabilirsiniz hemde güncelleştirmeleri kolayca takip edebilirsiniz. Aşağıda anlatacağım adımları takip ettiğiniz taktirde passenger-install-apache2-module veya passenger-install-nginx-module araçlarını kullanmanıza gerek kalmayacaktır.

İlk olarak PGP anahtarını sisteminize ekleyin:

Passenger reposu HTTPS’ten bağlantı kurduğu için sisteminize HTTPS desteğini kurun:

/etc/apt/sources.list.d/ klasörü altında passenger.list isminde bir dosya oluşturun ve kurulumu yapmak istediğiniz işletim sistemine uygun olarak aşağıdaki satırlardan sadece bir tanesini! ekleyin:

passenger.list dosyasının izinlerini düzenleyin ve paket listenizi güncelleyin:

Nginx paketlerini kurun:

Daha sonra, /etc/nginx/nginx.conf dosyasını düzenleyerek, passenger_root ve passenger_ruby değişkenlerini uncomment edin.

Önerdiğim şekilde Ruby kurulumunu rbenv aracı ile yaptıysanız, bu iki değişken şu şekilde olacaktır:

Nginx’i restart edin:

Şuan herşey yolundaysa nginx çalışıyor olmalıdır.

Passenger’ın çalışması için yapmamız gereken bir kaç şey kaldı:

Hepsi bu kadar. Şuan nginx ve passenger sorunsuz çalışıyor olmalı.

Nginx konfigürasyonu şurada bulunur:

Nginx hata logları şurada bulunur:

Deploy işlemleri ve Nginx konfigürasyonunu bir sonraki yazıda anlatacağım.

Rbenv ile Ruby ve Rails Kurulumu – Debian 7

Rbenv Kurulumu

Rbenv, RVM gibi ancak RVM’ye göre çok daha basit bir şekilde, birden çok Ruby versiyonunu birlikte kullanmak için geliştirilmiş bir araçtır. Sisteminizde tek bir Ruby sürümüne ihtiyaç duyuyorsanız dahi, rbenv ile kurulum yapmanızı öneririm.

Öncelikle rbenv’i /home/foo altına klonlayın:

Daha sonra PATH’e, rbenv değişkenlerini ekleyin:

Ruby sürümleri ve diğer şeyler için otomatik tamamlamayı etkinleştiren aşağıdaki komutu çalıştırın:

Terminalinizi yeniden başlatın. Daha sonra;

komutunu çalıştırdığınızda:

çıktısını görüyorsanız kurulum başarıyla tamamlanmış demektir.

Rbenv’in ruby-build eklentisini kurarak, Ruby kurulumu için ortamı hazır edelim:

Rbenv’in çalışma mantığı gereği, herhangi bir GEM kurduktan sonra her seferinde “rbenv rehash” komutunu çalıştırmamız gerekiyor. Ancak bu can sıkıcı bir iş olduğu için, bunun içinde bir eklenti yazmışlar. Bunu da sistemimize çekelim:

Rbenv kurulum işlemleri bu kadar. Şimdi Ruby’yi kuralım.

Ruby Kurulumu

Ruby için gerekli temel paketleri kurun:

Rbenv’de listelenen Ruby sürümlerine bakın:

Bunlardan kurmak istediğiniz sürümü kendiniz seçebilirsiniz. Ben bu yazının yazıldığı tarihteki en kararlı sürüm olan Ruby 2.1.5’i kullanacağım:

Herşey yolundaysa sisteminize Ruby kurulmuş olmalıdır. Rbenv ile, kurulan bu Ruby sürümünü ister sistem çapında, istersenizde uygulama çapında kullanabilirsiniz.

Sistem çapında bu sürümü kullanmak için:

Uygulama çapında kullanmak için, uygulamanızın olduğu dizine düştükten sonra, o dizin içerisinde:

komutlarını kullanabilirsiniz.

Kurulumu kontrol edin:

Rails Kurulumu

Rails kurulumunda özel bir durum bulunmamaktadır. GEM olarak kurmanız yeterlidir:

Kurulumunuz başarıyla tamamlandıysa “rails -v” komutu ile kurulmuş olan Rails sürümünü görebiliyor olmanız gerekir.

Debian ve Ubuntu – ElasticSearch Kurulumu

ElasticSearch kurulumu yapabilmek için öncelikle sisteminizde OpenJDK kurulu olmalıdır. Kurulum için: http://www.serhatdundar.com/debian-icin-openjdk-kurulumu

OpenJDK’yı kurduktan sonra ElasticSearch kurulumuna geçebilirsiniz.

Öncelikle GPG anahtarını ekleyin:

Aşağıda ki satırı /etc/apt/sources.list dosyanıza ekleyin:

Kurulumu gerçekleştirin:

Sistem başladığında elasticsearch hizmetinin de otomatik olarak başlamasını istiyorsanız:

veya manuel olarak elasticsearch’ü başlatmak için;

Şuan herşey yolundaysa ElasticSearch çalışıyor olmalıdır. Rails uygulamalarınızda ElasticSearch ile arama yapabilmek için modelinizde ElasticSearch’ü include etmeniz gerekir. Örneğin “User” modelimize bunu include edelim:

Ardından, arama yapacağınız controller action’unda (çoğunlukla index) ElasticSearch’ü çağırmanız gerekmektedir:

Rails console’dan elasticsearch’ün çalışıp çalışmadığına aşağıdaki şekilde bakabilirsiniz;

Herşey yolundaysa status olarak green dönecektir. Elasticsearch çalışıyor ancak performans sorunları varsa yellow dönecektir.

Bonus: View Tarafı:

Rails – N+1 Sorguları ve Çözümü

Veritabanınızda 5 tane “exam_center” kaydı olduğunu varsayalım.

ve aşağıda ki gibi bir model yapınız olduğu;

Bu exam_center’ların tamamını bir index sayfasında göstermek istediğinizde, her bir exam_center için 1 tane country, 1 tanede city sorgusu yapılacaktır. Yani her bir kayıt için view’dan çağırdığınız ilişki sayısı kadar sorgu yapılacak. 5 tane veritabanı kaydımız olduğu için, toplamda 10 sorgu çekilecek ve ek olarak ilk sorgu olan exam_center’ın kendisinin sorgulanmasıyla birlikte toplamda 11 sorgu edecektir. Buna kısaca N+1 durumu ismi verilir.

Uygulamanız içerisinde controller ve view dosyalarınızın bu şekilde olduğunu varsayalım:

Bu yapıda kurgulanmış bir sistemde index metodunu çağırdığımda loglarıma şu sorgular düşüyor:

5 veritabanı kaydı için çekilen 11 sorguyu görebilirsiniz.

includes Metodu

N+1 sorgu durumundan kurtulmak için “includes” metodu kullanılır.

Controller’ı bu şekilde düzenleyerek tekrar logları kontrol edelim:

Yapılan sorgu sayısı 7’ye düştü ancak hala Country için yapılan sorgularda bir değişiklik bulunmuyor. Burada “nested” bir lişki yapısı olduğu için dökümantasyonu takip ederek controller’ı tekrar düzenlememiz gerekecek:

Tekrar loglarımızı kontrol ettiğimizde yapılan sorgu sayısının 3’e düştüğünü ve sorgular için harcanan toplam sürenin azaldığını görebilirsiniz:

Hepsi bu kadar. Detaylar için Rails dökümantasyonuna göz atabilirsiniz:

http://guides.rubyonrails.org/active_record_querying.html

GEM – Pundit ile Authorization (Cancan Alternatifi)

Neden Cancan değilde Pundit?

  • Cancan 1 yılı aşkın süredir güncellenmiyor. Pundit sık sık güncelleniyor.
  • GEM’in geliştiricisi olan Ryan Bates’in uzun zamandır kayıplara karışmış durumda. Ryan Bates ayrıca Railscasts için 2 Eylül’de dönüş yapacağını söylemişti ancak 11 Temmuz itibariyle hala dönüş yapmadı ve kendisinden haber yok. Pundit’in arkasında İsveç kökenli elabs firması var.
  • Cancan’i başka sürdüren, sahip çıkan kimsenin olmaması sebebiyle açık olan onlarca issue bulunuyor. An itibariyle cancan’de 257, pundit’te ise 20 tane açık issue var. Cancan’e yapılan pull request’ler uzun bir süredir merge edilmemiş.
  • Rails 4.1 yayında olmasına rağmen cancan hala Rails 3 üzerini ve strong parametreleri desteklemiyor. Pundit ise Rails 4 destekliyor ve pure ruby yapısından dolayı uzunca bir süre versiyon sorunu çıkarmayacak gibi duruyor.
  • Cancan’in çok magic bir yapısı var. Pundit’te işler daha sizin kontrolünüzde.
  • Pundit cancan’e göre çok daha hafif ve hızlı çalışan bir GEM.
  • Pundit’in bağımlılıkları Cancan’in bağımlılıklarından daha az.
  • Pundit sayesinde authorization yapınızı hiç modele ve controller’a bulaşmadan – dolayısıyla fat controller’lar – modeller yaratmadan kurabiliyorsunuz.

Meraklısına ilave okumalar:

  1. Cancan vs. Pundit: http://www.distilnetworks.com/cancan-vs-pundit-choose-pundit-authorization/
  2. Alternative for Cancan: http://stackoverflow.com/questions/7213927/alternative-for-cancan/10235613#10235613
  3. Cookie HQ’ın Pundit Deneyimleri: http://cookieshq.co.uk/posts/pundit/
  4. Cancan’den Pundit’e Migration: http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/

Kurulum

application_controller‘ı aşağıdaki şekilde düzenleyin:

generator’ı çalıştırarak örnek dosyaları oluşturun:

Kısa Bir Ara (Planlama)

User” isminde bir kullanıcı modeliniz olduğunu ve şöyle bir yetkilendirme sistemi hazırlamak istediğinizi varsayalım:

auth

Böyle bir yapıyı 2 şekilde kurgulayabilirsiniz:

  1. User modelinize her bir yetki ismi için boolean bir alan oluşturursunuz ve bu sayede

    gibi sorgu yaparsınız.
  2. Roller için ayrı bir model oluşturursunuz ve her bir yetkiyi orada rol olarak tanımlarsınız ve

    gibi sorgu yaparsınız. Roller ve User modelleri HABTM ilişkisine sahip olur.

Uygulamanızın ihtiyaçlarına göre herhangi bir yolu seçebilirsiniz. İlk yöntem daha basit authorization işlemleri için yeterliyken, kapsamlı ve onlarca rolün olacağı bir sistemde ikinci yöntemi tercih etmenizi öneririm.

Kullanım

Generator’ın oluşturduğu dosya/lar GEM’in özelliklerini demo etmek ve size hazır bir iskelet yapıyı sunmak için düzenlendiğinden bunları incelemek kafanızı karıştırabilir, şimdilik boşverin.

Generator tarafından app/policies dizinine oluşturulan application_policy.rb dosyası authorization sisteminiz için default ayarlarınızı tanımlayacağınız dosyadır. Burada her bir CRUD işlemi için uygulamanın nasıl davranacağını kurgulayabilirsiniz.

Koddan anlayabileceğiniz üzere her bir controller metodu için burada bir metod tanımlıyoruz ve hangi durumlarda true, hangi durumlarda ise false döndüreceğini belirtiyoruz.

Bu genellenmiş modelin işinizi görmediği durumlar için, bu modeli miras alan ayrı ayrı policy’ler oluşturabilirsiniz.

Örneğin City isminde bir modeliniz olduğunu ve burada application_policy‘nin özelliklerini yine devralmak istediğinizi ancak ek ihtiyaçlarınız olduğunu varsayalım. Bu durumda app/policies altında “city_policy.rb” isminde bir dosya oluşturmanız yeterlidir. Veya;

komutu ile bu modele özel bir policy dosyası hızlıca oluşturabilirsiniz.

edit? metodu city_policy içerisinde tekrar tanımlandığı için application_policy‘de ki kullanımı göz ardı edilecektir. Buna benzer modele özel tanımlamaları kendi policy dosyası içerisinde yapabilirsiniz.

Şimdi tanımlamalarımız tamam olduğuna göre “cities_controller.rb” dosyasına gidelim:

Ve hepsi bu kadar. Artık basit düzeyde fakat sorunsuz çalışan bir yetkilendirme sisteminiz var.

Permission Denied Hatasını Ayıklama

Tanımladığınız durumlar dışında CRUD üzerinde yetkisiz bir işlem yapıldığında uygulamanız Pundit::NotAuthorizedError hatası vererek işlemi sonlandıracaktır. Bu hatayı yakalayarak, 403 sayfamıza yönlendirmesini sağlayalım:

application_controller.rb içerisinde aşağıdaki tanımlamayı yapınız;

Scopelar ve diğer konular için GEM dökümantasyonunu takip edebilirsiniz:

https://github.com/elabs/pundit

Kolay gelsin.