Accessing PostgreSQL through JDBC via a Java SSL tunnel

ArticleCategory:

System Administration

AuthorImage:[Here we need a little image from you]

[Photo of the Author]

TranslationInfo:

original in en Chianglin Ng
en to tr  Özcan Güngör

AboutTheAuthor:

Ben Güneydoğu Asya'da çokuluslu modern bir ülke olan Singapur'da yaşıyorum.Son iki yıldır Linux kullanıyorum.RedHat 6.2 ile başladım ve şu an evimde RedHat 8.0 kullanıyorum.Arasıra Debian kullanıyorum.

Abstract:

Bu makale, JDBC'nin RedHat 8.0'da PostgreSQL'e bağlanması için nasıl ayarlanacağını ve bu veritabanına güvenli bağlanmak için SUN'ın Java Güvenli Soket Eki'ni kullanarak nasıl SSL tüneli oluşturulacağını anlatır.

ArticleIllustration:[One image that will end up at the top of the article]

[Illustration]

ArticleBody:

Tanıtım

Postgres ve JDBC hakkında birşeyler öğrenirken, bir veritabanına JDBC ile güvenli nasıl bağlanılır sorunuyla karşılaştım.JDBC bağlantısı şifrelenmemiştir ve ağ sniffer bilgileri kolayca alır.Bunu önlemenin birçok yolu vardır.Postgres kullanma klavuzunda postgresin SSL desteği ile derlenmesini veya SSH tüneli kullanılmasını söyler.

Bu yöntemlerin kullanılması yerine, Java'nın kendisini kullanmak isterim.Sun'ın Java JDK 1.4.1 versiyonu java güvenli soket ekini içerir.Bununla kendi SSL bağlantımızı yapabiliriz.JDK, bir de açık/gizli anahtar oluşturabilmek için keytool, dijital sertifikalama ve anahtar depolamayı içermektedir.Bu araçlarla, verilern güvenli geçişini sağlayacak java tabanlı bir proxy yazmak mümkündür.

RedHat 8.0'da JDBC için PostgreSQL Ayarları

Burada anlatılanlar sadece RedHat içindir ama genel fikir diğer dağıtımlara da uygulanabilir.Eğer henüz yapmadıysanız, önce PostgreSQL'i ve gerekli JDBC süsücülerini yüklemeliyiz.RedHat 8'de, rpm'i veya paket yönetim GUI'sini kullanabilirsiniz.Ayrıca Sun'ın JDK 1.4.1'i de indirip yüklemelisiniz.JDK, US'in ihraç kanunları yüzünden bazı şifreleme kısıtlamarıyla gelmektedir.Tam şifreleme desteği için JCE (Java Şifreleme Eki)'yi indirmelisiniz.Daha fazla bilgi için Sun'in Java sayfasına bakınız.

Ben JDK 1.4.1'i kurdum ve JAA_HOME çevresel değişkenini JDK dizinini gösterecek şekilde değiştirdim.Bir de PATH değişkenini JDK çalıştırılabilir dosyalarını da içerecek şekilde ayarladım.Aşağıda .bash_profile dosyama eklediğim satırla bulunmkatdır:

JAVA_HOME = /opt/j2sdk1.4.1_01
PATH = /opt/j2sdk1.4.1_01/bin:$PATH
export JAVA_HOME PATH

SUN JDK ile gelen sınırlı şifreleme politikası dosyalarını sınırsız JCE dosyaları ile değiştirdim.Java'nın Postgres'e ulaşacak JDBC sürücülerini bulabilmesi için, postgre-jdbc sürücülerini Java ekleri dizinine (/opt/j2sdk1.4.1_01/jre/lib/ext) kopyaladım.RedHat 8'de postgres-jdbc sürücüleri /usr/share/pgsql dizinindedir.

Eğer ilk defa Postgres yüklüyorsanız, önce bir veritabanı ve kullanıcı hesabı oluşturmalısınız.su komutu ile root kulancı olun ve postgres servisini başlatın.Daha sonra su ile postgres yöntici kullanıcısı olun.

su root
password:******
[root#localhost]#/etc/init.d/postgresql start
[root#localhost]# Starting postgresql service: [ OK ]
[root#localhost]# su postgres
[bash]$

Create a new postgres account and database.

[bash]$:createuser
Enter name of user to add: chianglin
Shall the new user be allowed to create databases? (y/n) y
Shall the new user be allowed to create more new users? (y/n) y
CREATE USER
[bash]$createdb chianglin
CREATE DATABASE

Ben kendi Linux kullanıcılarıma uyan bir postgres yönetici kullanıcısı ve aynı isimde bir veritabanı oluşturdum.Varsayılan olarak, psql araçlarını kullandığınızda, veritabanına bağlanırken Linux o an hangi kullanıcı iseniz o kullanıcıyı kullanır.Veritabanı ve kullanıcı yönetimi için klavuz sayfalarına (man pages) bakın.Yeni kullanıcıya şifre vermek için psql'i çalıştırın ve ALTER USER komutunu çalıştırın.Normal kullanıcı olarak login olun ve psql'i çalıştırın.Aşağıdaki komutu yazın:

ALTER USER chianglin WITH PASSWORD 'test1234' ;

TCP/IP bağlantısına izin vermek için postgresql.conf dosyasını açın ve tcpip_socket seçeneğini true yapın.RedHat 8'de, bu dosya /var/lib/pgsql/data altnda bulunuz.root kullanıcı olun ve aşağıdaki değişikliği yapın:

tcpip_socket=true

Son adım pg_hba.conf dosyasını düzenlemektir.Bu dosya Postgres'e bağlanabilecek makinaları gösterir.Ben kendi loopback adresimi ve şifre yetilendirmesini ekledim.Bu dosyayı değiştirebilmek için root olmalısınız:

host sameuser 127.0.0.1 255.255.255.255 password

Postgres'i yeniden başlatın.

Java SSL Tüneli Tasarımı

Bir önceki adımdan sonra, Postgres'e güvenli olmayan local JDBC bağlantısı kurabiliriz.Postrgres'e uzaktan güvenli bağlanabilmek için çeşitli veri geçişi gereklidir.

Aşağidaki diagram, bu veri geçişinin nasıl çalışacağını gösterir:

Figure one showing how the Java proxies should work

JDBC uygulaması önce bir istemci proxy'ye bağlanır ve bu proxy verileri SSL aracılığıyla uzaktaki sunucu proxy^ye gönderir.Sunucu proxy basitçe verileri Postgres'e yönlendirir ve sonuçları SSL aracılığı ile istemci proxy'ye gönderir.İstemci proxy de bu sonuçları JDBC'ye geçirir.JDBC uygulamasından tüm işlem şeffaftır.

Diagramdan da anlaşılacağı gibi, sunucu tarafında, sunucuya güvenli bağlantıyla gelen verilerin alınması ve asıl yerel sunucuya gönderilmesi gerekir.Tersi de doğrudur,asıl yerel sunucudan gelen verileri güvenli bağlantı ile diğer tarafa göndermesi gerekir.Aynı durum istemci tarafında da geçerlidir.Bu işi için thread'ler kullanılır.Aşağıdaki diagram bunu gösterir:

Diagram showing how the 4 relaying threads work

Anahtar Depoları, Anahtarlar ve Sertifikalar Oluşturma

Bir SSL bağlantısı genellikle sunucu yetkilendirmesi gerektirir.İstemci yetkilendirmesi isteğe bağlıdır.Bu durumda, ben hem sunucu hem de istemci yetkilendimesini öneriyorum.Bunun anlamı, hem istemci için hem de sunucu için sertifikalar ve anahtarlar oluşturacağım.Bunu JDK ile gelen keytool sayesinde yapabiliyorum.Hem sunucuda hem istemcide anahtar depolarım olacak.Birinci depo makinanın gizli anahtarını, ikincisi ise makinanın güveneceği sertifikayı tutar.

Aşağıdaki komut sunucu için bir anahtar deposunun, bir gizli anahtarın ve bir kendi-imzalı (self-signed) sertifikanın oluşturur:

keytool -genkey -alias serverprivate -keystore servestore -keyalg rsa -keysize 2048

Enter keystore password: storepass1
What is your first and last name?
[Unknown]: ServerMachine
What is the name of your organizational unit?
[Unknown]: ServerOrg
What is the name of your organization?
[Unknown]: ServerOrg
What is the name of your City or Locality?
[Unknown]: Singapore
What is the name of your State or Province?
[Unknown]: Singapore
What is the two-letter country code for this unit?
[Unknown]: SG
Is CN=ServerMachine, OU=ServerOrg, O=ServerOrg, L=Singapore, ST=Singapore, C= [no]: yes
Enter key password for <serverprivate>
(RETURN if same as keystore password): prikeypass0 </serverprivate>

Şİfrelerin iki defa istendiğine dikkat edin.İlki anahtar depo için, ikincisi gizlia anahtar içindir.Bunları yaptıktan sonra Sunucu sertifikasını (bu istemci için sunucu yetkilendirmesinde kullanılacak) bir dosyaya yazdırmalıyız.

keytool -export -alias serverprivate -keystore -rfc servestore -file server.cer

Yukarıdaki komut, sunucunun kendi-imzalı açık sertifikasını server.cer dosyasına yazar.İstemci tarafında, bu dosyayı, istemcini güvendiği açık sertifkaları tutan bir anahtar deposuna koymalıyız.

keytool -import -alias trustservercert -file server.cer -keystore clienttruststore

Yukarıdaki komut, sunucunun açık sertifikasını clienttruststore adı verilen bir anahtar deposuna koyar.Eğer bu depo henüz oluşturulmamışsa, bu depo oluşturulur ve bu depo için bir şifre girilmesi istenir.

Şu durumda, sisteminiz sunucu yetkilendirmesi için bir SSL bağlantısı kurabilir.Ben ayrıca istemci için de yetkilendirme istediğim için, istemci için de yeni istemci anahtar deposunda açık/gizli anahtar oluşturmalı, iştemci sertifikasını bir dosyaya yazdırmalı ve bu dosyayı yeni sunucu anahtar deposuna koymalıyım.

Bu işlemler sonunda, sunucuda iki anahtar deposu olmalı.Biri gizli anahtar diğeri sertifikaları tutar.Bu istemci için de geçerlidir.

İleride vereceğim örnek kodu çalıştırmak için, belirli bir makinada oluturduğunuz her iki anahtar deposu için de aynı şifreyi vermelisiniz.Bunun anlamı sunucudaki iki anahtar deposunun da aynı şifreye sahip olması gerekir.Bu istemcideki ahatar depoları için de geçerlidir.

keytool hakkında daha çok bilgi için Sun'ın dokümanlarınaSun'n dokmanlarna bakn. bakın.

Sınıfların Uygulaması

Benim sınflarım, Java Güvenli Soket ekinin kullanım olacaktır.Sun JSSE referan saysında mevcuttur.Bir SSL bağlatısı için, JSSE'de bulunan SSLContext nesnesinin örneğini(instance) almaya gereksinim vardır.İstediğiniz ayarlarla başlattığınız SSLContext nesnesinden bir SecuredSocketFactory sınıfı elde edersiniz.SocketFactory ile bir SSL soketi oluşturabilirsiniz.

Benim uygulamamda, SSL tüneli oluşturacak bir istemci ve bir sunucu proxy'si olacaktır.Her ikisi de SSL bağlantısı kullanacağından, SSLConnection sınıfına benzeyecektir(interitance).Bu sınıf, hem sunucu hem istemci proxi'sinde kullanılacak olan SSL Context nesnesinin başlangıç ayarlarndan sorumludur.Son olarak, Veri geçişini yapacak olan başka bir thread'e gereksinimimiz vardır.Toplam 4 sınıf.Aşağıda SSLConnection sınıfının bir parçası vardır:

 /* initKeyStore metodu, gizli anahtarı ve güvenilir sertifikaları tutan anahtar depolarını yükler*/

public void initKeyStores(String key , String trust , char[] storepass)
{
      // mykey, kendi sertifikamı ve gizli anahtarımı tutar; mytrust, güvenilir bütü sertifikalrı tutar.
 try {
      //Sun JKS anahtar depolarının örneğini a
l      mykey = KeyStore.getInstance("JKS" , "SUN");
     mytrust = KeyStore.getInstance("JKS", "SUN");

    //Anahtar depolarını yükl
    mykey.load(new FileInputStream(key) ,storepass); mytrust.load(new FileInputStream(trust) ,storepass );
    }
 catch(Exception e) {
    System.err.println(e.getMessage());
    System.exit(1);
    }
}

/* initSSLContext method, SSLContext oluşturur ve onu SSL protokolü ve anahtar depolarından alınan verilerle başlatır.*
/ public void initSSLContext(char[] storepass , char[] keypass) {
    try{
    //Sun JSSE'den SSLContext'i al
   ctx = SSLContext.getInstance("TLSv1" , "SunJSSE") ;
   //Anahtar depolarını başla
   initKeyStores(key , trust , storepass) ;

    //Anahtar ve güvenilirlik depolarındaki serfikalarla ilgilenmesi için ahatar ve güvenilirlik yönetici fabrikalarını oluştu
r     TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509" ,
    "SunJSSE");
    tmf.init(mytrust);

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509" ,
    "SunJSSE");
    kmf.init(mykey , keypass);

    //SSLContext'i anahtar depolarında alın verilerler başla
t     ctx.init(kmf.getKeyManagers() , tmf.getTrustManagers() ,null) ;
    }
    catch(Exception e) {
    System.err.println(e.getMessage());
    System.exit(1);
    }

}

init SSLContext metodu, Sun JSSE'den bir SSLContext oluşturur.Bu oluşturma sırasında, kullanılacak SSL protokolünü belirtebilirsiniz.Bu durumda, ben TLS(Transport Layer Security-Ulaştırma Katmanı Güvenliği) sürüm 1'i seçiyorum.SSLContext örneği bir kere elde edildiğinde, anahtar deposundaki verilerle başlatılır.

Aşağıdaki kod parçası, Postgres ile aynı makina üzerinde çalışan SSLRelayServer sınıfına aittir.Bu, istemciden SSL aracılığı ile gelen bütün verileri Postgres'e geçirir veya tam tersini yapar.

SSLRalyServer sınıfı


/* initSSLServerSocket, SSLContext'in üst sınıf olan SSLConnection aracılığı ile SSLContext'i alır.Daha sonra bu, SSLServerSocket oluşumak için kullanılacak olan SSLServerSocketFactory oluşturur.*
/ public void initSSLServerSocket(int localport) {
      try{
           //get the ssl socket factory
           SSLServerSocketFactory ssf = (getMySSLContext()).getServerSocketFactory();

            //create the ssl socket
           ss = ssf.createServerSocket(localport);
           ((SSLServerSocket)ss).setNeedClientAuth(true);
      }
   catch(Exception e) {
      System.err.println(e.getMessage());
      System.exit(1);
    }
 }

// SSLServerSocket ile dinleye başla ve gelecek istemci bağlantılarını bekl
e public void startListen(int localport , int destport) {

    System.out.println("SSLRelay server started at " + (new Date()) + "  " +
                     "listening on port " + localport + "  " +  "relaying to port " + destport );

 while(true) {
      try {
         SSLSocket incoming = (SSLSocket) ss.accept();
         incoming.setSoTimeout(10*60*1000); // set 10 minutes time out
         System.out.println((new Date() ) + " connection from " + incoming );
         createHandlers(incoming, destport); // create 2 new threads to handle the incoming connection
       }
    catch(IOException e ) {
        System.err.println(e);
        }
    }
}

RelayApp sınıfı, yani istemci proxy'si, SSLRelayServer'a benzer.SSLConnection'ın alt sınıfıdır ve asıl veri geçişini sağlamak için 2 thread kullanır.Aralrındaki fark şudur:RelayApp, gelen bağlantıları dinlemek için SSLServerSocket yerine daha çok uzak makinala bağlanmak için SSLSocket oluşturur.Gereksinim duyduğumuz son sınıf, asıl veri geçişini yapacak olan thread'dir.Basğt olarak, gelen verileri ekrana yazdıracaktır.

Anlatılan dört sınıfın tam kodu bburada (example285-0.1.tar.gz). bulabilirsiniz.

Proxy'leri Çalıştırma ve Test Etme

İstemci üzerinde, SSLConnectin.java,RelayIntoOut.java ve RelayApp.java dosyalarına gerekesinim vardır.Sunucu tarafında, SSLRelayServer.java, RelayIntoOut.java ve SSLConnection.java dosyalarına gereksinim vardır.Bunların hepsini aynı dizin içine koyun.İstemci proxy'sini derlemek için aşağıdaki komutu çalıştırın:

javac RelayApp.java

Sunucu proxy'si için ise şu komutu çalıştırın:

javac SSLRelayServer.java

Postgres'in çalıştığı sunucuda, SSLRelayServer'i altı komut parametresi ile çalıştırız:

1. Daha önce keytool ile oluşturduğunuz gizli anahtarı tutan anahtar deposunun tam yolu
2. Sunucunun güvenilir istemci sertifikalarını tutan sunucu anahtar depolarının tam yolu
3. Ahatar depolarının şifreleri
4. Sunucunuzun gizli anahtarının şifresi
5. Sunucunun dinleme yapacağı port numarası
6. Verilerin yönlendirileceği port numarası ( asıl sunucunun port numarası, bu durumda postgres varsayılan olarak 5432 nolu porttan dinleme yapar)
java SSLRelayServer servestore trustclientcert storepass1 prikeypass0 2001 5432

Sunucu proxy'sini çalıştırdıktan sonra istemci proxy'sini çalıştırabiliriz.İstemci proxy'si yedi parametre alır.Yedinci argüman bağlanılacak olan sunucunun makina ismi ya da IP adresidir:

1. İstemcinin gizli anahtarını tutan anahtar deposunun tam yolu
2. Güvenilir sunucuların sertifikalarını tutan istemci anahtar deposunun tam yolu
3. Anahtar deposunun şifresi
4. İstemci gizli anahtarının şifresi
5. Sunucunun makina ismi veya IP adresi
6. Veri geçişi yapacak olan sunucunun port numarası
7. Veri geçişinin yapılmasını istediğiniz uygulamanın port numarası ( bu durumda postrgesdir.5432 nolu port)
java RelayApp clientstore trustservercert clistorepass1 cliprikeypass0 localhost 2001 5432

SSL tüneli oluşturulduktan sonra, JDBC uygulamasını çalıştırabilir ve Postgres'e normal yoldan bağlanabilirsiniz.Bütün veri geçişi işlemleri JDBC uygulaması açısından şeffaftır.Bu makale zaten uzun oluğundan JDBC uygulamasının örnek kodlarını veremeyeceğim.Postgres kullanım klavuzu ve Sun dokümanları birçok JDBC örneği vermektedir.

Eğer test sırasında herşeyi aynı makina üzerinde çalıştırmak istiyorsanız, yapabilirsiniz.Bun yapmanın iki yolu varsır:Ya Postgres'in dinleme yaptığı port değiştirirsiniz ya da RelayApp'nin veri geçişi yaptığı portu değiştirisiniz.ikici yolun nasıl kullanılacağını göstereceğim:Önce RelayApp'yi durdurun, sadece crtl-c'ye basın.Aynı şeklide SSLRelayServer'i de durdurabilirsiz.

Aşağıdaki komutla RelayApp'i çalıştırın.Tek değişiklik son paratmede.Artık 2002'dir.

java RelayApp clientstore trustservercert clistorepass1 cliprikeypass0 localhost 2001 2002

Test için kullanılacak en iyi uygulama psql'in kendisidir.Tm psql trafiğini, tünelimiz aracılığıyla postrges'e geçireceğiz.Test için psql'i çalıştımak için aşağıdaki komutu çalıştırın:

psql -h localhost -p 2002

Bu komut, psql bağlantısını RelayApp'mizin dinlediği yerel port 2002'ye yönlendirir.Postgres şifrenizi girdikten sonra, her zamanki yolla SQL komutlarını çalıştırmaya başlayabilirsiniz ve şu an veri geçişi yapan SSL bağlantısını test edebilirsiniz.

Güvenlik Üzerine Bir Not

Eğer makinanızı başkalarıyla paylaşıyorsanız, şifrenizi bir komut satırında açık vermeniz uygun olmaz.Çünkü eğer biri ps -auxww komutu çalıştırırsa komutları tüm parametreliyle görebilir.en iyi yol şifrenizi şifreli(encrypted) olarak başka bir dosyada bulundurmak ve java uygulamanızın şifrenizi bu dosyadan okumasını sağlamaktır.Başka bir yol da Java Swing'i kullanarak şifrenizin bir pencere içerisinde sorulmasıdır.

Sonuç

Postgres tarafından kullanılabilen bir SSL tünelinin Sun JSSE kullanarak oluşturlması kolaydır.Aslında, her güvenli bağlantıya gereksinim duyan uygulama bu tüneli kullanabilir.Bağlantınızı şifrelemenin birçok yolu vardır.Sadecen sevdiğiniz editör programını açın ve kodlamaya başlayın.İyi eğlenceler!

Yararlı Linkler