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.
0 Respuestas a “Tocando openssh”