Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

Autentificación y autorización con mod_python (II)

Última Actualización: 18 de octubre de 2005 - Martes

Hace dos años y medio, presenté un proyecto de autentificación Apache basado en mod_python.

El proyecto original tiene dos problemas:

  • Cambiar los usuarios autorizados y/o los intervalos temporales de activación supone modificar el código fuente del "handler" Python. Ello implica que o bien el administrador del dominio debe ponerse en contacto (y, por tanto, consumir tiempo) del administrador de sistemas que hospeda el servicio, o bien éste debe dar permiso de modificación del handler al administrador del dominio, lo que le permitiría alterar el código python de manera maliciosa.

  • El "handler" reside físicamente en el espacio de ficheros del web hospedado. Eso implica que el administrador del dominio tiene acceso al código del mismo. Y aunque no pueda modificarlo directamente, por no tener permiso para ello, puede eliminar el directorio que lo contiene y recrearlo, pudiendo meter un script arbitrario en su lugar.

Adicionalmente, los dos problemas anteriores conspiran para impedir que los diferentes "handlers" gemelos que puedan existir en varios dominios o directorios de un mismo dominio se puedan compartir, ocasionando una pesadilla de mantenimiento y seguridad.

Ambos problemas, una vez identificados, tienen en realidad una solución bastante sencilla.

El primer problema se soluciona haciendo que los datos de autorización "usuario", "contraseña" e intervalo temporal de validez, se lean desde un fichero externo, de texto puro (no un "script"). De esa forma el administrador del dominio puede realizar cambios sin la intervención del administrador de sistemas. El nuevo código del "handler" es el siguiente:

# Autentificacion controlada por horarios
# y con acceso directorio a directorio

from mod_python import apache

# Autorizacion
def authzhandler(request) :
  #return verifica_usuario(request)
  return apache.OK

# Autentificacion
def authenhandler(request) :
  return verifica_usuario(request)


def verifica_usuario(request) :
  usuarios={}
  try :
    import os
    f=request.filename
    f=f[:f.rfind(os.sep)+1]+"claves.txt"
    f=open(f)
    for i in f :
      i=i.strip()
      if (not i) or i[0]=="#" : continue
      i=i.split(",")
      assert len(i[2])==12
      assert len(i[3])==12
      j=long(i[2]) # Comprobamos que sean numeros, aunque la comparacion
      k=long(i[3]) # posterior sera entre cadenas
      usuarios[i[0]]=(i[1],i[2],i[3])
  except :
    request.content_type = "text/html"
    request.send_http_header()
    request.write("<h1>ERROR EN EL FICHERO DE CLAVES!!</h1><br><br><br>")
    return apache.HTTP_UNAUTHORIZED

  try :
    # Para que nos de el usuario, primero hay que pedir la clave
    clave=request.get_basic_auth_pw()
    usuario=request.connection.user

    if not usuarios.has_key(usuario) : return apache.HTTP_UNAUTHORIZED
    i=usuarios[usuario]
    if i[0]!=clave : return apache.HTTP_UNAUTHORIZED
    import time
    now=time.strftime("%Y%m%d%H%M")
    if (now<i[1]) or (now>i[2]) : return apache.HTTP_UNAUTHORIZED
    return apache.OK
  except :
    # Si ocurre algo raro, nos curamos en salud
    return apache.HTTP_UNAUTHORIZED

El fichero que contiene los parámetros de autentificación se llama "claves.txt", reside en el directorio a proteger, y tiene el formato siguiente:

# La sintaxis es la siguiente:
# usuario,clave,tiempo de inicio,tiempo de final
# los tiempos se especifican como YYYYMMDDHHMM.

# Los comentarios empiezan con '#'.

# La siguiente linea define un usuario "prueba",
# con clave "prueba4" de validez todo el 2005
#prueba,prueba4,200501010000,200601010000

# El siguiente usuario solo tiene acceso desde
# las 4 de la tarde del 26 de octubre a las 9
# de la noche de 2 de diciembre:
# usuario,clave,200510261600,200512022100

Por supuesto, se pueden definir múltiples usuarios, cada uno con su propia clave e intervalo de validez. Basta con especificar cada uno de ellos en una línea.

En cuanto a la configuración Apache en sí, lo evidente es añadir lo siguiente:

<Directory el que sea>
<Files "claves.txt">
Order Allow,Deny
Deny from all
</Files>
Auth_MySQL      off
# Eliminamos la busqueda en el directorio actual
PythonPath "sys.path[1:]+['path al directorio donde reside el "handler"']"   ¡OJO!: Esto es incorrecto. ver párrafos siguientes
PythonAuthenHandler     autorizacion_horaria
PythonAuthzHandler      autorizacion_horaria
AuthType        Basic
AuthName        "Realm que queramos usar"
#AuthUserFile   "/dev/null"
#AuthGroupFile  "/dev/null"
#AuthAuthoritative      off
require user
</Directory>

Respecto a la versión anterior, se prohibe el acceso al fichero "claves.txt" a través del servidor web, por razones evidentes, y se modifica el "PythonPath" para, por un lado, eliminar la búsqueda en el directorio actual y, por otro, añadir la búsqueda en el directorio de referencia de "scripts" y "handlers", fuera de los ojos y los dedos de los usuarios.

La configuración anterior es técnicamente correcta, pero tiene un problema con la directiva "PythonPath". Dicha directiva se reevalúa en cada ejecución del "handler", por lo que nos iremos cargando el path de Python y acabará dejando de funcionar. O no, porque como la evaluación de "PythonPath" es bastante lenta, en realidad "mod_python" hace caché del resultado de una directiva "PythonPath" y reutiliza el resultado.

Pero las cosas se complican cuando se utilizan varios "handlers", ya que cada uno evalúa el "PythonPath" por separado, pero el resultado es compartido (lo que considero que es un bug de "mod_python", por cierto). Es decir, que si un "handler" altera el path de búsqueda de Python, este cambio afecta al resto de "handlers".

Para solucionar el problema, lo más seguro es utilizar un "PythonPath" global en la configuración del Apache, que afecte a todos los "handlers", o bien utilizar "PythonPath" privados pero estáticos y explícitos.

De hecho, si se emplea la configuración mostrada, tal cual, y tenemos varios "handlers" que la emplean, su funcionamiento será errático y esporádico, según el orden en que se vayan accesiendo las páginas y el proceso Apache concreto que enganchemos en cada petición. ¡NO LO HAGAS!. Como ya he dicho, entiendo que se trata de un bug en "mod_python", que podría estar solucionado ya cuando leas esto :-).


Historia

  • 18/Oct/05: Primera versión de este documento.

  • 18/oct/05: Documentamos el problema de la manipulación del "PythonPath", y damos una alternativa que funciona bien.



Python Zope ©2005 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS