|
|
Este documento está disponible en los siguientes idiomas: English Castellano Deutsch Francais Nederlands Russian Turkce |
por Chianglin Ng <chglin(at)singnet.com.sg> Sobre el autor: Vivo en Singapur, un país moderno multiracial localizado en el Sudeste de Asia. He estado usando linux por los últimos dos años aproximadamente. La primera distribución con la que empezé fue redhat 6.2. Hoy uso redhat 8.0 en casa. Tambié uso debian woody ocasionalmente. Taducido al español por: Juan Carlos Piedra <jcpiedra(at)troglo.org> Contenidos: |
Accessing PostgreSQL through JDBC via a Java SSL tunnelResumen:
Este artículo muestra cómo configurar accesso JDBC
para PostgreSQL en redhat 8.0 y cómo crear un túnel SSL
usando las Extensiones de Sockets Seguros Java de Sun, para
habilitar el acceso seguro a una base de datos postgres remota.
|
Las instrucciones dadas aquí son para redhat 8.0 pero los principios generales son aplicables a otras distribuciones. Usted necesita instalar PostgreSQL y los manejadores correspondientes de JDBC si no lo ha hecho todavía. En redhat 8, ud. puede usar el comando rpm e instalar el JDK 1.4.1 de Sun. El JDK de Sun viene con algunas restricciones criptográficas debido a las regulaciones de exportación de los Estados Unidos. Para conseguir criptografía avanzada puede descargar los ficheros de políticas del JCE (en inglés: Java Cryptographic Extensions, Extensiones de Criptografía Java). Visite el sitio web Java de Sun para más información.
He instalado el JDK 1.4.1 en /opt y configurado la variable de ambiente JAVA_HOME para apuntar a mi directorio JDK. También he actualizado mi trayectoria (PATH) para incluir el directorio que contiene los ejecutables de JDK. Las líneas siguientes fueron añadidas a mi fichero .bash_profile.
JAVA_HOME = /opt/j2sdk1.4.1_01
PATH = /opt/j2sdk1.4.1_01/bin:$PATH
export JAVA_HOME PATH
Los limitados ficheros de políticas de cifrado que vienen con el JDK de Sun han sido reemplazados con los ficheros ilimitados en el JCE. Para habilitar java para que encuentre los manejadores para postgres, copié los manejadores postgres-jdbc en mi directorio de extensiones de Java (/opt/j2sdk1.4.1_01/jre/lib/ext). En redhat 8.0, los manejadores postgres-jdbc están localizados en /usr/share/pgsql.
Si esta es su primera instalación postgresql, usted tendrá que crear una nueva base de datos y una nueva cuenta de usuario. Primero, utilice el comando "su" para obtener acceso al super usuario (root) y dé inicio al servicio postgres. Luego cámbiese a la cuenta de administración por defecto de postgres.
su root
password:******
[root#localhost]#/etc/init.d/postgresql start
[root#localhost]# Starting postgresql service: [ OK ]
[root#localhost]# su postgres
[bash]$
Hay que crear una nueva cuenta de usuario y una nueva base de datos de postgres.
[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
He creado una nueva cuenta de administrador de postgres que corresponde a mi cuenta de usuario de linux y una base de datos con el mismo nombre. Cuando ud. ejecuta la herramienta psql, tratará de conectarse a una base de datos que corresponde al nombre la cuenta de usuario actual. Refiérase al manual postgres para más detalles sobre la administración de cuentas y bases de datos. Para configurar una clave de acceso para la cuenta recién creada, puede ejecutar psql e invocar el comando ALTER USER. Ingrese al sistema usando su cuenta de usuario normal e inicie psql. Escriba el siguiente comando
ALTER USER chianglin WITH PASSWORD 'test1234' ;
Para permitir conexiones tcp/ip, ud. necesita editar el fichero postgresql.conf y habilitar la opción tcpip_socket. En redhat 8, este fichero está localizado en /var/lib/pgsql/data. Como super usuario (root) configure lo siguiente:
tcpip_socket=true
El último paso es editar el fichero pg_hba.conf. Este fichero especifica los ordenadores que pueden conectarse a la base de datos postgres. He añadido una única entrada para un ordenador especificando la dirección de lazo cerrado (loopback) de mi pc, usando autenticación con contraseña. Usted necesita cambiarse al super usuario para editar este fichero .
host sameuser 127.0.0.1 255.255.255.255 password
Reinicie postgres y todos los cambios de configuración
se harán efectivos.
Luego del paso anterior, postgres está listo para aceptar conexiones JDBC locales no seguras. Para accesar postgres de forma segura remotamente, alguna forma de repetición es requerida.
El siguiente diagrama muestra cómo esta repetición debería funcionar.
La aplicación JDBC se conectará al proxy cliente, el cual enviará todos los datos a través de la conexión SSL a nuestro proxy servidor remoto. El proxy servidor sencillamente remitirá todos los paquetes hacia postgres y enviará respuestas a través de la conexión SSL de vuelta al proxy cliente, el cual a su vez reenviará los datos a la aplicación JDBC. Este proceso será enteramente transparente para la aplicación JDBC.
En el diagrama se observa que desde el lado del servidor,
existirá la necesidad de obtener los datos del flujo
entrante seguro y enviarlos al flujo de salida local que se
encuentra conectado al servidor. Lo contrario también
es cierto, es necesario obtener los datos del flujo entrante local
conectado al servidor y enviarlos a flujo de salida seguro. El
mismo concepto también aplica para el cliente. Para
implementar esto se pueden utilizar subprocesos. El siguiente
diagrama lo muestra
Una conexión SSL usualmente require autenticación
del servidor. La autenticación del cliente es opcional. En este
caso, prefiero tener ambas autenticaciones, servidor y cliente. Esto
significa que tengo que crear certificados y llaves para el cliente y para
el servidor. Hago esto usando la herramienta para llaves (keytool)
incluida en el JDK de Java. Tendré un par de almacenes de
llaves (keystores), uno en el cliente y otro en el servidor.
El primer almacén de llaves es necesario para guardar la llave
privada
de el servidor y el segundo para almacenar los certificados en los
que confía el servidor.
Lo siguiente muestra la creación de un almacén de llaves,
una llave privada y un certificado público firmado para el
servidor.
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>
Observe que las contraseñas son requeridas dos veces. La primera es para el almacén de llaves y la segunda para la llave privada. Una vez hecho esto, se exporta en un fichero el certificado público del servidor que será utilizado por el cliente para autenticar al servidor.
keytool -export -alias serverprivate -keystore -rfc servestore -file server.cer
Lo anterior exportará el certificado público firmado del servidor hacia el fichero server.cer. Desde el cliente importe este fichero en el almacén de llaves que guarda todos los certificados públicos en los que el cliente confía.
keytool -import -alias trustservercert -file server.cer -keystore clienttruststore
El comando anterior importará el certificado público del servidor en un almacén de llaves llamado clienttruststore. Si este almacén no existe aun, será creado y ud. requerirá ingresar una contraseña para el almacén.
En este punto, su sistema será capaz de facilitar una
conexión SSL que provee la autenticación del
servidor.
Debido a que también quiero autenticar al cliente,
necesitaré
crear una llave privada y otra pública para el cliente en un
nuevo almacén, exportar el certificado público del
cliente, e importarlo en un nuevo almacén de llaves en
el servidor.
Al final de este proceso, deben existir dos almacenes de llaves
en el servidor, uno contiene su llave privada y el otro contiene
los certificados fiables. Lo mismo ocurre en
el cliente.
Para poder ejecutar el código de ejemplo que doy más adelante, es esencial que ud. utilice la misma contraseña para cada almacén de llaves que crea en cada máquina. Esto significa que los dos almacenes de llaves en el servidor deben tener la misma contraseña. Lo mismo aplica para los dos almacenes de llaves en el cliente.
Puede referirse a la documentación de Sun para aprender más sobre el uso de la herramienta para llaves (keytool).Mis clases utilizarán las extensiones de Sockets Seguros Java de Sun (Java Secured Socket extensions, en inglés). La guía de referencia para JSSE de Sun está disponible en http://java.sun.com/j2se/1.4.1/docs/guide/security/jsse/JSSERefGuide.html. Para una conexión SSL, ud. necesita una instancia del objeto SSLContext suministrado por JSSE. Inicialice SSLContext con las opciones que desee y obtendrá una clase SocketFactory segura. La clase SocketFactory puede ser usada para crear los sockets SSL.
En mi implementación, existirá una clase proxy
para el servidor y una para el cliente, con el fin de construir el
túnel SSL. Ya que ambos utilizarán una conexión
SSL, deben heredar de una clase SSLConnection base. Esta clase
será responsable de configurar el SSLContext inicial que
será usado para el proxy del cliente y el del servidor.
Finalmente, necesitamos implementar otra clase para los subprocesos
repetidores. En total son 4 clases.
Lo siguiente muestra la sección de código perteneciente
a la clase SSLConnection
Sección de Código de la clase SSLConnection
/* initKeyStore método para cargar los almacenes
de llaves que contienen la llave privada y los certificados fiables
*/
public void initKeyStores(String key , String trust , char[]
storepass)
{
// mykey contiene mi propio certificado y llave
privada,
mytrust contiene todos los certificados de confianza
try {
//obtener instancias del almacén de llaves JKS
de Sun
mykey = KeyStore.getInstance("JKS" ,
"SUN");
mytrust = KeyStore.getInstance("JKS",
"SUN");
//cargar los almacenes de llaves
mykey.load(new
FileInputStream(key) ,storepass);
mytrust.load(new FileInputStream(trust) ,storepass
);
}
catch(Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
/* initSSLContext método para obtener un SSLContext e
inicializarlo con el protocolo SSL y los datos de los almacenes de llaves
*/
public void initSSLContext(char[] storepass , char[] keypass)
{
try{
//obtener un SSLContext del JSSE de Sun
ctx = SSLContext.getInstance("TLSv1" , "SunJSSE")
;
//inicializar los almacenes de llaves
initKeyStores(key , trust , storepass) ;
//Crear la llave y las trust manager
factories para manejar los certificados
//en la llave y los almacenes de confianza
TrustManagerFactory tmf =
TrustManagerFactory.getInstance("SunX509" ,
"SunJSSE");
tmf.init(mytrust);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance("SunX509" ,
"SunJSSE");
kmf.init(mykey , keypass);
//Inicializar el SSLContext con los datos de los almacenes
de llaves
ctx.init(kmf.getKeyManagers() ,
tmf.getTrustManagers() ,null) ;
}
catch(Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
El método initSSLContext crea un SSLContext
del JSSE de Sun. Durante la creación, ud. puede especificar el
protocolo SSL a utilizar. En este caso, he escogido usar TLS
(Transport Layer Security) versión 1. Una vez que
una instancia del SSLContext se obtiene, se inicializa con los
datos de los almacenes de llaves.
La siguiente sección de código pertenece a la
clase SSLRelayServer que funcionará en la misma
máquina que la base de datos postgres. Esta clase
retransmitirá toda la información del cliente
desde la conexión SSL hacia postgres y viceversa.
Clase SSLRelayServer
/* initSSLServerSocket método que obtendrá
el SSLContext de su super clase SSLConnection. Luego creará un
objeto
SSLServerSocketFactory que será utilizado para crear un
SSLServerSocket. */
public void initSSLServerSocket(int localport) {
try{
//obtener
la fábrica de sockets ssl
SSLServerSocketFactory
ssf = (getMySSLContext()).getServerSocketFactory();
//crear el socket ssl
ss
= ssf.createServerSocket(localport);
((SSLServerSocket)ss).setNeedClientAuth(true);
}
catch(Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
// comenzar a escuchar en el SSLServerSocket y esperar por conexiones
entrantes de clientes
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); // tiempo de expiración en
10 minutos
System.out.println((new Date() ) + " connection from " +
incoming );
createHandlers(incoming,
destport); // crear 2 nuevos subprocesos para manejar las conexiones
entrantes
}
catch(IOException e ) {
System.err.println(e);
}
}
}
La clase RelayApp, el proxy cliente, es similar a SSLRelayServer.
Hereda de SSLConnection y usa 2 subprocesos para hacer el
reenvío. La diferencia consiste en que crea un SSLSocket
para conectarse al servidor remoto en lugar de un SSLServerSocket
para recibir conexiones entrantes. La última clase que
necesitamos es el subproceso que hace el reenvío efectivo.
Sencillamente lee los datos de un flujo de entrada y los escribe a un
flujo de salida.
En el cliente, ud. necesitará estos ficheros SSLConnection.java, RelayIntoOut.java y RelayApp.java. En el servidor, ud. necesita SSLRelayServer.java, RelayIntoOut.java y SSLConnection.java. Póngalos juntos en un directorio. Para compilar el proxy cliente, ejecute el siguiente comando.
javac RelayApp.java
Para compilar el proxy servidor, ejecute lo siguiente
javac SSLRelayServer.java
En nuestra máquina servidor ejecutando postgres, se puede iniciar SSLRelayServer con 6 parámetros en la línea de comandos. Estos son
java SSLRelayServer servestore trustclientcert storepass1 prikeypass0 2001 5432
Una vez que el proxy servidor está en ejecución, ud. puede dar inicio al proxy cliente. El proxy cliente recibe 7 parámetros, los adicionales son el nombre del servidor o dirección IP del servidor al que se está conectando. Los argumentos son
java RelayApp clientstore trustservercert clistorepass1 cliprikeypass0 localhost 2001 5432
Una vez que el túnel SSL es establecido, ud. puede dar inicio a su aplicación JDBC y conectarse a postgres de la manera usual. El proceso de reenvío será transparente para su aplicación JDBC. Este artículo ya es muy largo y no daré ejemplos para una aplicación JDBC aquí. El manual de postgres y la guía de aprendizaje de Sun contienen muchos ejemplos sobre JDBC.
Si ud. quiere ejecutar todo en una única máquina durante el período de pruebas, lo puede hacer. Hay dos maneras para hacerlo, una es configurar su base de datos postgres para que escuche en un puerto diferente, o puede cambiar el puerto que RelayApp usa para enviar datos a otro puerto diferente. Puedo usar lo último para ilustrar una prueba simple. Primero, cierre RelayApp, necesitará enviarle una señal de terminación presionando [ctrl] c. Utilize el mismo método para detener el proxy SSLRelayServer.
De inicio a RelayApp de nuevo con el siguiente comando. El único cambio es el número de puerto, ahora es 2002.
java RelayApp clientstore trustservercert clistorepass1 cliprikeypass0 localhost 2001 2002
La mejor aplicación que se puede usar para pruebas es el mismo psql. Enviaremos todo el tráfico de psql hacia postgres a través de nuestro túnel. Ejecute el siguiente comando para dar inicio a psql para las pruebas.
psql -h localhost -p 2002
Este comando ordena a psql a conectarse a localhost en el puerto 2002 en el cual nuestro RelayApp está escuchando. Luego de entrar su contraseña de postgres, puede comenzar a ejecutar comandos SQL de forma usual y probar la conexión SSL que está ahora haciendo el reenvío.
No es buena idea especificar contraseñas como parámetros en la línea de comandos si ud. comparte un ordenador. Esto debido a que alguien que ejecute el comando ps -auxww será capaz de ver la línea de comandos completa de su proceso, incluyendo las contraseñas. Es mejor almacenar las contraseñas en una forma no cifrada en otro fichero y dejar a su aplicación de java leerlas a partir ahí. Alternativamente puede usar Swing de Java para crear una caja de diálogo que pida una contraseña.
Es simple usar el JSSE de Sun para la creación de un
túnel SSL que puede ser usado por postgres. De hecho,
cualquier otra aplicación que requiera de una conexión
segura puede probablemente utilizar este túnel SSL. Existen
muchas formas de agregar criptografía a su conexión,
solo use su editor de linux favorito y empieze a escribir código.
Diviértase!
|
Contactar con el equipo de LinuFocus
© Chianglin Ng, FDL LinuxFocus.org |
Información sobre la traducción:
|
2003-03-24, generated by lfparser version 2.34