GECO's blog

El proyecto sigue avanzando paso a paso. Hoy he implementado un frontend para el demonio de GECO (gecod) que implementa una interfaz XMLRPC con ssl.
Dado que vamos a mandar información sensible hacia el servidor es necesario que la conexión se haga a través de un canal seguro, por lo tanto he buscado por ahí la forma de tener un servidor xmlrpc en python sobre ssl y lo he encontrado.
A partir de ese código he creado el módulo python secure_xmlrpc. Y básandome en este, crear un servidor XMLRPC sobre ssl es tan fácil como declarar una clase y pasarle una instancia de esta al constructor de la clase EasyServer.
... import secure_xmlrpc as sxmlrpc ... class frontend: ... def auth(self, user, password): ... def start_server(): sxmlrpc.EasyServer(HOST, PORT, frontend()) ...
Por otra parte, en el lado del cliente, he empezado creando un módulo (utils) el cual ahora mismo tiene un generador de contraseñas "aleatorias" y una función que comprueba la seguridad de una contraseña de forma simple.
Para generar una contraseña aleatoria he usado los módulos random y string. Está parametrizado el conjunto de caracteres a utilizar y el tamaño de la contraseña generada. Con random.choice y una lista por comprensión genero la contraseña a partir del conjunto de caracteres seleccionado.
import random import string LOWER, UPPER, DIGITS, PUNCT = (string.lowercase, string.uppercase, string.digits, '.:;,!?{}[]<>=-_()+') def generate(size=11, lower=True, upper=True, digits=True, punctuation=False): chars = '' selection = [lower, upper, digits, punctuation] strings =(LOWER, UPPER, DIGITS, PUNCT) for opt, v in zip(selection, strings): if opt: chars += v return ''.join([random.choice(chars) for _ in xrange(size)])
Para comprobar la seguridad de las contraseñas he implementado una función que comprueba características básicas de las contraseñas y devuelve un valor entre 0 y 1.
Los test que he implementado son:
- contraseña en una lista de palabras
- la contraseña es una secuencia ordenada
- según el tamaño (<=4, <=6, <=8, >=14)
- la contraseña contiene algún caracter (minusculas, mayusculas, digitos, puntuación)
def strength(password): ''' return 0..1 ''' strength = -5 # bad passwords tests (-20 or -15) bad_passwords = ('', 'qwerty', 'asdf', 'zxcv', '123', '1234') if password in bad_passwords: return -20 ord_pass = [ord(i) for i in password] inverted_pass = ord_pass[:] inverted_pass.reverse() if is_sorted(ord_pass) or is_sorted(inverted_pass): return -15 # len tests (min -10, max 5) if len(password) <= 4: strength -= 10 elif len(password) <= 6: strength -= 8 elif len(password) <= 8: strength -= 5 elif len(password) >= 14: strength += 5 # chars tests (max +20) for chars_string in LOWER, UPPER, DIGITS, PUNCT: for j in chars_string: if j in password: strength += 5 break return ((strength + 20) / 40.0) def is_sorted(alist): ''' alist is a list of ints ''' first = alist[0] i = 1 while i < len(alist): if first != alist[i] - 1: return False first = alist[i] i += 1 return True
Con esto y un bizcocho, podremos generar contraseñas y verificar su seguridad con suma facilidad.

Ya he creado la primera versión de la base de datos, con sqlalchemy, para el demonio del gestor de contraseñas GECO.
La base de datos es necesaria para almacenar las contraseñas ya cifradas, así como los usuarios que están registrados en ese demonio.
En principio sólo tenía pensado crear tres tablas, la de los usuarios, la de las contraseñas y la de los ficheros de configuración, pero he añadido una cuarta (cookies).
Las tres primeras están más o menos justificadas dada la aplicación que me he propuesto desarrollar. Necesito guardar los usuarios registrados. Para cada usuario tendré que almacenar las contraseñas y ficheros de configuración que gestione con la aplicación.
La cuarta viene dada por la forma en la que estoy implementando el backend del demonio. La idea es que el frontend sea totalmente "independiente" del backend, sólo haga llamadas a una serie de funciones. He pensado que una buena (y simple) manera de hacer esto es mediante un sistema de cookies similar al que utilizan los navegadores, para así evitar que el frontend esté continuamente pasandole la contraseña del usuario (aunque sólo sea un hash de la contraseña real) al backend (aunque no haya transferencia por la red).
Por lo tanto con el sistema de cookies que he implementado, el frontend llamará a la función autenticar (que admitirá más de un método de autenticación, actualmente sólo está implementado por usuario y contraseña) y esta función devolverá una cookie. Para cada petición que quiera hacer el frontend al backend, en lugar de pasar usuario y contraseña, sólo tiene que pasar la cookie.

De momento hay poco código, pero el sistema va tomando forma, y estoy intentando mantenerlo lo más simple posible. Tengo que escribir un poco más de código y de documentación para poder recibir colaboraciones, dado que de momento la idea está en mi cabeza, por lo que no hay una forma sencilla de colaborar en el proyecto.
Estoy manteniendo tanto el repositorio en la forja de rediris como en mi propio servidor, y para ello estoy utilizando bzr-svn, con lo que con un "bzr push" hago un commit en la forja de rediris.

En el esquema de la arquitectura de GECO aparece como una parte bastante importante en el demonio "gecod" SQLObject.
Un ORM simplifica el acceso a una base de datos por parte del programador convirtiendo toda sentencia sql a operaciones con objetos. Así por ejemplo añadir una fila a una tabla es tan fácil como crear un objeto.
Después de algún tiempo pensandolo he decidido que sería mejor opción utilizar SQLAlchemy, ¿por qué este cambio? Bueno, son varias razones las que me han llevado finalmente a explorar este nuevo ORM:
- Es más potente que SQLObject
- Ya conocía SQLObject, una oportunidad de conocer otro ORM
He estado mirando un poco la documentación de SQLAlchemy y voy a explicar de forma breve la manera más sencilla de utilizar este ORM en python.
Definición de una tabla/objeto
from sqlalchemy import * from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relation, backref, sessionmaker Base = declarative_base() metadata = Base.metadata class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String(20)) fullname = Column(String(50)) def __init__(self, name, fullname): self.name = name self.fullname = fullname def __repr__(self): return "<User('%s','%s')>" % (self.name, self.fullname) db = create_engine('sqlite:///database.sqlite', echo=False) metadata.create_all(db)
Así de fácil se declara un simple objeto. Esto se refleja en una base de datos con una tabla, de nombre 'users', con las columnas id, name y fullname.
Con metadata.create_all(db) se crea la tabla en la base de datos (La base de datos puede ser sqlite, mysql, postgresql,...).
Creación de objetos
Session = sessionmaker(bind=db, autoflush=True) session = Session() u = User('dani', 'daniel garcia') session.add(u) session.commit()
De esta forma tan simple se pueden crear usuarios que se verán reflejados en la base de datos relacional como una nueva fila en la tabla de usuarios.
Es necesario crear un objeto de tipo session para interactuar con la base de datos.
Y para eliminar una entrada nada más simple que
session.delete(u) session.commit()
Consultas
session.query(User).filter(User.name == 'dani').all()
Se pueden hacer multitud de consultas con filter y además se pueden concatenar filters -> filter(User.name == 'dani').filter(User.fullname == 'daniel garcia')....
Relaciones
class Address(Base): __tablename__ = 'addresses' id = Column(Integer, primary_key=True) email_address = Column(String, nullable=False) user_id = Column(Integer, ForeignKey('users.id')) user = relation(User, backref=backref('addresses', order_by=id, cascade='all, delete-orphan', passive_deletes=False)) def __init__(self, email_address): self.email_address = email_address def __repr__(self): return "<Address('%s')>" % self.email_address
Si queremos declarar una tabla que esté relacionada con otra se utiliza ForeignKey. Además hay que definir una relación, y backref nos da la referencia en la tabla padre, es decir que la tabla padre tendrá este atributo. Con cascade y passive_deletes se consigue el borrado en cascada, cuando se borre un usuario se borraran todas los emails asociados a este.
u = session.query(User).filter_by(name='dani').one() print u.addresses u.addresses.append(Address('dani@danigm.net')) print u.addresses session.commit() session.query(User, Address).filter(User.id == Address.user_id).filter(Address.email_address == 'dani@danigm.net').first()
Con este ejemplo se ve como se pueden hacer consultas sobre la unión de varias tablas.

Intro
GECO pretende ser un gestor de contraseñas y ficheros de configuración distribuido con diferentes interfaces de comunicación con el usuario. Presentanto tanto una interfaz de escritorio en GTK como una interfaz web desarrollada con python y cherrypy.
GeCo es un proyecto presentado a la tercera edición del Concurso Universitario de Software Libre y nace de la necesidad de tener contraseñas aleatorias y diferentes para cada servicio web (o no) en el cual estás registrado.
Problema
Hoy en día la mayoría de la gente que navega con regularidad tiene multitud de cuentas creadas en diferentes páginas webs, estas cuentas, normalmente, están basadas en usuario y contraseña. Las contraseñas se almacenan de diferentes formas en las bases de datos de los diferentes servicios, de tal forma que no podemos saber si el administrado o un intruso puede llegar a ver nuestra contraseña.
Por tanto es realmente importante hoy en día utilizar claves diferentes (y aleatorias) para cada aplicación, pero memorizar 5,10,20 claves seguras es realmente complicado y para esto están los gestores de contraseñas.
Puesto que el uso de gestores de contraseñas implica que no recuerdes tus contraseñas estás perdiendo la movilidad y la libertad de usar diferentes máquinas en las cuales no tengas instalado tu gestor de contraseñas con tu base de datos de contraseñas.
Además en el caso de rotura de tu disco duro puedes llegar a perder la base de datos de tu gestor de contraseñas perdiendo así para siempre muchas contraseñas que no podrás recuperar.
Solución
GeCo pretende dar solución a los problemas que presentan los gestores de contraseñas de escritorio dando un paso más allá y prestando un servicio de almacenamiento y gestión de contraseñas distribuido (o centralizado) con diferentes interfaces de administración (línea de comandos, escritorio, web, móvil, etc).
No voy a poner TODAS mis contraseñas en ningún servidor
Por supuesto la seguridad y privacidad es uno de los objetivos de este proyecto, por tanto toda información que salga del cliente irá cifrada con una clave maestra, y por tanto en el servidor no se almacenará información recuperable sin esta contraseña.
Arquitectura
GeCo implementará una arquitectura clásica cliente-servidor, de tal forma que habrá un servidor GeCo al cual se podrán conectar clientes GeCo, para administrar contraseñas, y también otros servidores GeCo para sincronizar datos.
De esta forma un usuario puede tener su servidor GeCo en su máquina y utilizarlo normalmente como un gestor de contraseñas normal, pero además puede sincronizar su servidor con otro sevidor en una máquina remota, de tal forma que si el usuario se mueve pueda tirar de la interfaz web del servidor de la máquina remota para administrar sus contraseñas.
¿Algo más?
Además de todo esto GeCo ofrecerá la posibilidad de almacenar ficheros de configuración (todo cifrado), de tal forma que en un linux con un cliente GeCo puedas utilizar tu fichero de configuración de Pidgin sólo durante la sesión y posteriormente se eliminaría todo rastro de tus ficheros de configuración y el sistema quedaría tal y como estaba.
Licencia y otras polladas legales
Todo lo que se desarrolle será bajo licencia GPLv3 y todo el artwork y documentación será totalmente libre con la licencia "úsalo cómo quieras y para lo que quieras".
La idea de este proyecto es hacerme un gestor de contraseñas para mí, y si le sirve a otra persona pues mejor. Además quiero hacerlo de forma didactica para que a partir de este proyecto salgan diferentes manuales y charlas sobre python y desarrollo.




