Archive Page 3

Sweetter 2.0

En el autobús, camino del Congreso de Hispalinux (cáceres), surgió la idea de implementar un twitter libre. Y durante todo el fin de semana es a lo que nos hemos dedicado.

Nuestra implementación no tiene como objetivo copiar a twitter, también tenemos pensadas muchas funcionalidades, para hacer más divertido el uso de sweetter.

En principio en sweetter puedes escribir hasta un máximo de 133 caracteres, tenemos pensado que varíe segun el karma. Y este karma se calculará a partir de los followers que tengas y de los votos a tus comentarios (esta parte está por implementar).

Ayer le introduje la posibilidad de poner TODO, así se puede utilizar como un sistema de asignación de tareas.

Y con las cosas que tenía ya implementadas, y el dominio comprado, pues ayer lo publicamos en SUGUS http://sweetter.net. Penyaskito ya nos advirtió de que antes de publicar esperaramos a la auditoría que él nos iba a hacer, pero no podíamos esperar, así que ayer por la tarde estaba funcionando.

Y como buen hacker, Penyaskito encontró un fallo en mi código (una cosa arto dificil), y se ha adueñado de la página durante toda la noche. Me comentó por donde había entrado, y ya lo he solucionado.

Anuncios

TrayIcon en python

En sugus hace poco hemos puesto un sensor de presencia, que basicamente es una webcam que está mirando hacia la pared, y con un programita hecho en python, con pygame, se mira una imagen capturada por la webcam, y se calcula la luminosidad media. Si la luminosidad media es superior a 100, es que la luz de la habitación está encendida. En otro caso es que está apagada, y por tanto no hay nadie. Gracias a este sensor, en la web se puede observar una bolita, que estará roja, o verde, según no haya nadie, o haya alguien en sugus.

A partir de ahí, surgió la idea de llevar eso al escritorio, y como primera aproximación he hecho un icono del sistema en python. Para esto me he basado en uno que tenía por aquí, de un programa que gestiona la conexión a la red, el wicd.

Para poder hacerlo había que tener un acceso al estado de manera rápida y simple, por lo que hice este webservice:

http://sugus.eii.us.es/webservice/hayalguien.php

Que devuelve 1 cuando hay alguien, y 0 cuando no.

Aquí está el código:

import os,sys
import socket
import gtk, gobject, time

tr=None

dir = sys.argv[0]
final = len(dir)
for i in range(len(dir)):
    if dir[len(dir) -1 - i] == '/':
        final = len(dir) - i
        break
dir = dir[0:final]

def set_signal_image():
    s = socket.socket()
    try:
        s.connect(("sugus.eii.us.es", 80))
        s.send("GET /webservice/hayalguien.php\r\n")
        valor = s.recv(1)
        s.close()
    except:
        valor = '0'

    if valor == '1':
        tr.set_from_file(dir+"verde.png")
    else: tr.set_from_file(dir+"rojo.png")
    return True


class TrackerStatusIcon(gtk.StatusIcon):
    def __init__(self):
        gtk.StatusIcon.__init__(self)
        menu = '''
                <ui>
                <menubar name="Menubar">
                <menu action="Menu">
                <menuitem action="About"/>
                <menuitem action="Quit"/>
                </menu>
                </menubar>
                </ui>
        '''
        actions = [
                ('Menu',  None, 'Menu'),
                ('About', gtk.STOCK_ABOUT, '_About...', None, 'About wicd-tray-icon', self.on_about),
                ('Quit',gtk.STOCK_QUIT,'_Quit',None,'Quit wicd-tray-icon', self.on_quit),

                ]
        ag = gtk.ActionGroup('Actions')
        ag.add_actions(actions)
        self.manager = gtk.UIManager()
        self.manager.insert_action_group(ag, 0)
        self.manager.add_ui_from_string(menu)
        self.menu = self.manager.get_widget('/Menubar/Menu/About').props.parent
        self.current_icon_path = ''
        self.set_from_file(dir+"rojo.png")
        self.set_visible(True)
        self.connect('popup-menu', self.on_popup_menu)

    def on_quit(self,widget):
        sys.exit()

    def on_popup_menu(self, status, button, time):
        self.menu.popup(None, None, None, button, time)

    def on_about(self, data):
        dialog = gtk.AboutDialog()
        dialog.set_name('SUGUS sensor')
        dialog.set_version('1.0')
        dialog.set_comments('Este icono te dice si hay alguien en SUGUS')
        dialog.set_website('http://sugus.eii.us.es')
        dialog.run()
        dialog.destroy()

    def set_from_file(self,path):
        if path != self.current_icon_path:
            self.current_icon_path = path
            gtk.StatusIcon.set_from_file(self,path)

tr=TrackerStatusIcon()
gobject.timeout_add(3000,set_signal_image)
gtk.main()

El código con las imagenes, se puede encontrar aquí http://sugus.eii.us.es/~danigm/trayicon.tgz

Para que arranque por defecto, vete a las aplicaciones de arranque de tu escritorio favorito, y añade el comando personalizado:

python /ruta/te/hayas/bajado/esto/sugus.py

Y ya estarás informado en todo momento de cuando hay alguien en sugus, para pasarte por allí a hacer una visita.

Repositorios al estilo Subversion con Bazaar

Hoy he estado tocando un poco el control de versiones distribuido Bazaar. Bazaar es un sistema de control de versiones que se viene utilizando bastante desde hace algún tiempo, debido al uso del mismo en la plataforma launchpad que tienen montada los de ubuntu, y que funciona bastante bien.

Voy a explicar antes de nada qué es un sistema de control de versiones de código para el que no lo sepa. Un VCS (Version Control System) es una herramienta que te gestiona diferentes versiones de código, de tal forma que varias personas puedan trabajar sobre el mismo código, y después el VCS se encarga de mezclar las diferentes versiones, siendo así una herramienta indispensable para el desarrollo de aplicaciones en grupos de desarrollo. Además, entre muchas otras funcionalidades, esta herramienta permite obtener una versión antigua del proyecto de forma simple, así pues no hay miedo de estropear algo, ya que está en el repositorio, y todo es recuperable.

Ahora que sabemos lo que es un VCS, voy a explicar cómo usar bazaar para gestionar nuestras versiones de código. Lo primero es instalar bazaar, que se puede conseguir de su página web, o de los repositorios de tu distribución favorita, busca por bzr.

Vamos a suponer que tenemos un proyecto ya comenzado, en un directorio, llamemosle “proyecto”. Dentro de este directorio tenemos los ficheros de código fuente y demás cosas en las que estamos trabajando, y queremos versionar.

Primero hay que iniciar el repositorio, esto se hace con una orden muy simple, estando en el directorio raiz:

bzr init

Una vez hecho esto, ahora hay que decirle que ficheros queremos que controle, en nuestro caso todos:

bzr add *

El comando add añade ficheros al repositorio, por lo tanto cuando creemos ficheros o directorios nuevos utilizaremos este comando “bzr add fichero_nuevo” para que lo incluya en el control de versiones. Al igual que hay un add, existe un bzr rm, un bzr mkdir, y algunos más.

Ahora podemos ver el estado de nuestro repositorio con:

bzr status

Para que los cambios sean definitivos, debemos hacer un commit:

bzr commit

Esto nos abrirá un editor de textos, en el cuál pondremos lo que hemos cambiado, y esto saldrá en el log del repositorio.

Así ya tenemos un repositorio en nuestro ordenador, pero ahora nosotros queremos publicarlo en un servidor para que los demás desarrolladores puedan descargarse el código, modificar, y subir sus modificaciones:

bzr push sftp://danigm@servidor.com/home/danigm/public_html/proyecto

En este caso lo he subido a mi servidor, al cual tengo acceso por ssh. Esto me pedirá la contraseña para poder acceder, y ya estará mi proyecto subido. Con bzr push se pueden utilizar diferentes protocolos, como el ftp, bzr+ssh (este para launchpad)….
Una vez hecho el push deberás hacer un checkout para poder trabajar igual que trabajarías con subversion

Ahora es el momento de que otro desarrollador quiera pillar el código, cambiar algo, y subirlo, pues bien, esto se puede hacer de forma similar a cómo se haría con subversion:

bzr co sftp://dev2@servidor.com/home/danigm/public_html/proyecto

Aquí nos pedirá el password, para poder descargar el proyecto, y creará una copia local del mismo, donde cada commit que hagamos será publicado en el repositorio del servidor. También es posible que personas sin acceso al servidor puedan descargarselo, por medio de http:

bzr co http://servidor.com/~danigm/proyecto

Una vez el desarrollador2 tenga su copia de trabajo, puede modificar los ficheros, añadir, borrar, etc. Y cuando quiera que sus cambios se vean en el repositorio tiene que hacer un commit:

bzr ci

Se le pedirá la contraseña, y se le abrirá el editor de textos para que ponga el mensaje de log. Luego se sube la versión al repositorio.

Cuando tienes una copia del repositorio y algún otro desarrollador ha modificado algo, tienes que actualizarte para que se descarguen los últimos cambios:

bzr up

Para ver lo que ha cambiado, puedes mirar el log:

bzr log

Así podemos utilizar bazaar como un repositorio centralizado, al estilo de subversion, sin demasiada complicación. Además de esto bazaar nos permite crear ramas (branch), mezclar ramas (merge), y muchas cosas más, que se pueden ver en la ayuda, o en la documentación.

Tocando openssh

Siguiendo con el post de SSH a traves de claves RSA, voy a contar aquí cómo he tocado el servidor ssh, openssh, para que compruebe la clave pública RSA a través de un servidor de claves central.

Openssh son una serie de herramientas de código libre, para conexiones seguras a través de internet, a través del protocolo SSH.

Definamos primero los objetivos. Lo que busco es tener un servidor de claves RSA en cualquier parte, al cual le pregunte el servidor ssh por la clave de un usuario que intenta acceder, y el servidor de claves le devuelva la clave pública RSA de este usuario si lo conoce. Aquí hay dos partes claramente diferenciadas, que habrá que abordar de forma diferente. Por un lado tenemos el servidor de claves RSA, que es el que debe tener acceso a las claves públicas, y responder a las peticiones de los diferentes servidores ssh. Y por otro lado tenemos que modificar el servidor ssh, para que cuando esté haciendo la autenticación a través de las claves RSA, pregunte al servidor de claves por el usuario.

Servidor de claves RSA

Para este proposito he implementado un sencillo servidor de ejemplo en python. Para realizar las conexiones se usan sockets tcp/ip, sobre el puerto 12345. Hay dos clases, la clase ServidorRSA, que se encarga de reservar el puerto, y esperar las conexiones, cuando le llega una conexión, crea un hilo Responser que se encarga de gestionar la comunicación con el cliente. La clase Responser debe conocer las claves públicas de los usuarios, en este caso tan solo hay un usuario, con una clave. Para que fuera usable de verdad debería usar una base de datos o algo similar.

Para un uso real, también sería conveniente usar encriptación en las conexiones, como por ejemplo openssl, ya que van a circular nombres de usuarios, y claves públicas, que aunque no sean datos sensibles, es importante ocultar.

Para la comunicación entre cliente y servidor he definido un sencillo protocolo:
* Petición de clave de usuario: “USR:nombre_de_usuario”
* Respuesta: “clave RSA” o “NOT FOUND”
* Cierre de conexión: “XIT”

Aquí está el código de ejemplo:

#!/usr/bin/python

import sys
import socket
import pdb
from threading import Thread

class Responser(Thread):
    def __init__ (self, sock, addr):
        Thread.__init__(self)
        self.sock = sock
        self.sock.settimeout(60)
        self.addr = addr
        self.buffer = ''
        self.msg = []

    def run(self):
        print "inicio de " + str(self.addr) + "\n"
        while self.recv() and len(self.msg) > 0:
            command = self.msg.pop(0)
            while (command == '' and len(self.msg) > 0):
                command = self.msg.pop(0)
            if command == '': break
            if command[0:3].upper() == 'USR':
                user = command[4:]
                self.send(self.find(user))
            if command[0:3].upper() == 'XIT':
                break
        self.sock.close()
        print "fin de " + str(self.addr) + "\n"

    def recv(self):
        try:
            self.buffer = self.sock.recv(1024)
            self.msg.extend(self.buffer.split('\r\n'))
            return True
        except:
            return False

    def send(self, msg):
        try:
            self.sock.send(msg+'\r\n')
            return True
        except:
            return False

    def find(self, user):
        '''
        Busca un usuario en el almacen, y devuelve la clave RSA
        asociada. Si no lo encuentra, devuelve ''
        '''
        #TODO hay que utilizar una base de datos, o un ldap
        rsa = {'danigm': 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAsdYFDzHNJ7OTPTs8pfSW/1EtZKwzt9Fa9l3TkHf1fPCHvCdQzAGpovM
zQvFfmBP+jbh6IDEGzSK8f8fxx968EsOlzTrpBeBvNqSCFzKsQ8SQt4IBQ/Jr1oGVWcwEQWBGMcIgBaATqCW
FEiaWvBVTpHJDsB/mylDBmAO3QvxDZ1V6CaQBgKesocEljZ4FubefC4FOsEroo12Iamag/0T8S2jMFFne
84t+BwAPS+AnO2OtjMu7xDWFUmK1lm6qmRh2GdRjqda7cQ+FckyvNNcI69SRKBWcXMBSl4T/jecEJk
Q/6ZrLfm8EVUspSmunArbAnoEka6w== danigm@home'}

        try:
            rsakey = rsa[user]
        except:
            rsakey = 'NOT FOUND'

        return rsakey


class ServidorRSA:
    '''
    Servidor de claves RSA.
    Tiene un almacen de claves por usuarios autenticados en la
    federacion.
    Espera una peticion de usuario, y contesta con la clave RSA si lo
    tiene en el almacen.
    '''
    def __init__(self):
        self.port = 12345
        self.ss = socket.socket()
        try:
            self.ss.bind(('', self.port))
            self.ss.listen(5)
        except:
            print "Fallo al iniciar el servicio en el puerto %d" % \
            self.port
        try:
        self.respond()
        except:
            print "Fallo al intenter contestar a los mensajes"

    def respond(self):
        '''
        Espera una conexion, y cuando la recibe crea un thread para
        manejarla.
        '''
        while True:
            try:
                new_sock, new_addr = self.ss.accept()
                res = Responser(new_sock, new_addr)
                res.start()
            except:
                self.close_all()
                print "final de ejecucion"
                sys.exit(0)

    def close_all(self):
        self.ss.close()

if __name__ == "__main__" :
    srsa = ServidorRSA()

Modificación al servidor SSH (openssh)

Después de bajar el código, en mi caso a través de CVS, me puse a examinarlo en busca del fichero en el cual se miran las claves RSA. No es dificil encontrar esto, ya que los nombres son muy descriptivos, y en este fichero podemos encontrar lo más importante: “auth2-pubkey.c”.

Observando el código llegamos a la conclusión de que la parte más importante para mi proposito está en la función “user_key_allowed”, que mira en el fichero .ssh/authorized_keys, para ver si está la clave pública.

/* check whether given key is in .ssh/authorized_keys* */
int
user_key_allowed(struct passwd *pw, Key *key)
{
    int success;
    char *file;

    file = authorized_keys_file(pw);
    success = user_key_allowed2(pw, key, file);
    xfree(file);
    if (success)
        return success;

    /* try suffix "2" for backward compat, too */
    file = authorized_keys_file2(pw);
    success = user_key_allowed2(pw, key, file);
    xfree(file);
    return success;
}

file es la ruta hacia el fichero que contiene la clave RSA, y se pasa a la función user_key_allowed2. La solución que he implementado es la siguiente:

/* check whether given key is in .ssh/authorized_keys* */
int
user_key_allowed(struct passwd *pw, Key *key)
{
    int success;
    char *file;
    char rsa_key[600];
    FILE *error = fopen("/home/danigm/fed+ssh/opensshlog2","a");
    char *file2 = "/home/danigm/fed+ssh/tmp_file";
    FILE *tmp_file = fopen(file2,"a+");

    file = authorized_keys_file(pw);
    success = user_key_allowed2(pw, key, file);
    xfree(file);
    if (success)
        return success;

    /* try suffix "2" for backward compat, too */
    file = authorized_keys_file2(pw);
    success = user_key_allowed2(pw, key, file);
    xfree(file);
    if (success)
        return success;

// try external file fed+ssh <danigm>
// TODO el puerto y el host deben estar en el fichero de conf
    get_rsa_key("servidorRSA.com", 12345, pw->pw_name, rsa_key);
    if(strcmp(rsa_key,"") != 0){
        fprintf(tmp_file, "%s\n", rsa_key);
        fclose(tmp_file);
        success = user_key_allowed2(pw, key, file2);
        fprintf(error, "trying external public key %s\n", rsa_key);
        unlink(file2);
    }

    fclose(error);
    return success;
}

Hay unas cuantas lineas, que solo sirven para controlar los errores, así que pueden eliminarse. Lo importante está en que después del procedimiento normal, es decir, después de buscar la clave en los ficheros locales, hago una petición, con esta función get_rsa_key(“goonie.us.es”, 12345, pw->pw_name, rsa_key); al servidor de claves. Si el servidor devuelve una respuesta, escribo un fichero temporal, en el cual meto la clave pública, y hago la llamada a la función user_key_allowed2, para comprobar si es válida. De esta forma se consigue de forma simple lo que buscabamos.

Solo falta por implementar la funcion get_rsa_key, que está aquí:

//TODO hay que quitar esto de aqui, esta el fichero
//ssh_fed.c ssh_fed.h, que se deberia compilar con el
//makefile, y enlazar con el resto
//TODO hacerlo seguro, con openssl
int get_rsa_key(char *keyserver, int port, char *user, char *rsa_key){
    int sockfd, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;

    char ret[600];
    char msg[100];
    strcpy(ret,"");
    sprintf(msg, "USR:%s\r\n", user);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        return -1;

    if ((server=gethostbyname(keyserver)) == NULL)
        return -1;

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr = *((struct in_addr *)server->h_addr);
    memset(serv_addr.sin_zero, '', sizeof serv_addr.sin_zero);
    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof serv_addr) == -1)
        return -1;
····
    send(sockfd, msg, sizeof(msg), 0);
    if ((n=recv(sockfd, ret, 599, 0)) == -1)
        return -1;

    close(sockfd);
    strcpy(rsa_key, ret);
    return 0;

Ahora se lanza el servidor de claves en una máquina accesible. Se compila e instala el servidor ssh en todas las máquinas que quieras que den este servicio, y a partir de ahí, ya puedes entrar e todas tus máquinas por ssh, sin tener que teclear password, y pudiendo cambiar de máquina, tan solo controlando que llevas tu clave privada. Ya no es necesario modificar cada servidor ssh para cambiar el authorized_keys.

Esto es un ejemplo de como gracias al software libre, y con un poquito de esfuerzo se pueden adaptar grandes aplicaciones a tus necesidades concretas.

SSH a traves de claves RSA

Ssh (Secure Shell) es una herramienta bastante potente. Desde que uso Linux vengo utilizando ssh para administrar máquinas de manera remota, para enviar ficheros de una máquina a otra (sftp), etc. Ssh lo que hace básicamente es darte un terminal en una máquina remota, a través de una conexión segura. Además tienes la posibilidad de subir y bajar ficheros a traves del protocolo sftp, puedes importar las Xs y ejecutar aplicaciones con interfaz gráfico remotamente…

Últimamente estoy trabajando bastante más con ssh, y me he encontrado con una funcionalidad que no conocía, y es la posibilidad de poder entrar en tus máquinas remotas sin necesidad de introducir la contraseña.

¿Y eso cómo va a ser? Cualquiera podría entrar. Pues no, se utiliza un mecanismo de autenticación por contraseña publica-privada. Tú generas tu par de claves, publica y privada, con el comando ssh-keygen, y se te generarán en $HOME/.ssh/ dos ficheros, id_rsa e id_rsa.pub. Estas son tus claves privada y publica respectivamente. Para poder entrar en tu servidor remoto, tendrías que poner el contenido del fichero id_rsa.pub dentro del fichero $HOME/.ssh/authorized_keys del usuario del servidor remoto.

¿Cómo funciona?
Cuando un cliente ssh se conecta al servidor, este mira primero si hay alguna clave en el fichero authorized_keys del usuario. Si hay, le manda un mensaje cifrado, con la clave publica, al cliente, y si este tiene la clave privada, descifrará el mensaje, y se lo devolverá bien. Si la respuesta es correcta, no es necesario pedir contraseña, y se da acceso al usuario. Si el cliente no tiene la clave privada, se pasará al método tradicional, es decir, pedir contraseña.

Esto es muy bonito, y fácilita mucho la tarea de administración, pero baja la seguridad del sistema al utilizar estos métodos. El problema está en que si se utiliza RSA, hay que tener en cuenta que quien tenga la clave privada podrá entrar, por lo tanto hay que proteger la clave privada lo mejor posible.

Otro problema está en que sólo se puede acceder desde una máquina con este método, ya que la clave privada está en un fichero. Es posible añadir al authorized_keys varias claves publicas diferentes, una por linea. Esto nos reduce el problema, pero aún así es imposible entrar desde una máquina que no hayas añadido al fichero del servidor. Una posible solución sería introducir el fichero con la clave privada en un dispositivo portátil, como por ejemplo una memoría flash, y copiarlo al home del usuario en cuestión cuando se esté en una máquina ajena, etc.

¿Por qué cuento todo esto?
Porque a mí me interesa autenticar a un usuario para entrar por ssh sin tener que escribir la contraseña, y esto me lo da directamente. Para solucionar el problema de la dependencia de la modificación del authorized_keys en el servidor, he pensado que una buena solución sería que hubiera un servidor de claves públicas, al que le preguntara el servidor ssh por la clave del usuario. Y en lugar de tener que introducir las claves en el servidor ssh, las claves estarían en un servidor central. Y así se puede acceder a diferentes máquinas por ssh sólo añadiendo tu clave pública en un servidor, el servidor de claves, y no en todos y cada uno de los servidores ssh.

Tengo a medio implementar esta funcionalidad en el openssh, y también tengo un ejemplo de servidor de claves. En próximos post iré introduciendo el código y las dificultades con las que me he encontrado.

libertad?

Eres libre de hacer lo que quieras, hasta que te sales del camino predeterminado…

Winnie de Pooh

A mis veintitantos años me ha llegado una carta de winnie de Pooh, “¡Para descubrir el mundo divirtiendote!”