Cómo Excluir una URL o Parámetros de una URI de la Autenticación Básica de Apache con Apache Expressions

Cuando trabajamos con el módulo de Autenticación Básica de Apache, en ocasiones ocurre que necesitamos excluir ciertas rutas o parámetros de la URI que estamos protegiendo. Aquí detallo cómo lograrlo de la forma más sencilla posible, apelando a un nuevo feature de unificación introducido en la versión 2.4: Apache Expressions.

El resumen

Implementación Rápida
Dificultad Media

De dificultad media dado que el proceso requiere la previa implementación de autenticación básica de Apache y de conocimientos básicos en expresiones regulares.

La implementación es rápida dado que requiere de sólo una línea de código (una instrucción) para generar la regla.

Un problema recurrente: Recursos Compartidos

Un pequeño preámbulo. Si sos desarrollador de software, muchas veces te habrás enfrentado al dilema de compartir recursos entre partes de un mismo software. Esto plantea una facilidad y un obstáculo al mismo tiempo (recomiendo leer este buen artículo sobre Cohesión y Acoplamiento), ya que un módulo debe ser idealmente independiente (bajo acoplamiento) y tratar sobre una misma cosa (alta cohesión).

¿Por qué traigo esto a colación? No es que esté directamente relacionado, pero en algunas ocasiones, puede que el no manejar correctamente el scope de una aplicación web ocasione un mal routing (en las URLs) y devenga en problemas de recursos protegidos que no deben estarlo (o viceversa). Un ejemplo de esto ocurre con algunos plugins de Wordpress, que estando en el scope público utilizan recursos de nivel protegido (dentro del directorio /wp-admin/*). La consecuencia, para quienes agregamos una capa adicional de seguridad para el login del admin con autenticación básica (y evitar ataques innecesarios), es que algunos plugins pueden dejar de funcionar.

La solución: Apache Expressions, desde Apache 2.4

La directiva SetEnvIf

Apache permite la manipulación de acciones mediante directivas como SetEnvIf o con el módulo Rewrite (las más populares). La primera, permite setear el valor de una variable dependiendo de una condición. Esa variable puede utilizarse luego como condición para efectuar alguna acción u omitirla. Repasemos rápidamente un ejemplo para SetEnvIf:

AuthType Basic
AuthName "Protegido por contraseña"
AuthUserFile /var/www/htpasswd

SetEnvIf REQUEST_URI "^/admin/" IS_ADMIN

Deny from all
Satisfy any
Allow from env=!IS_ADMIN

Require valid-user

En el ejemplo, la primer parte inicializa la autenticación básica. Luego, SetEnvIf establece «si la URI comienza con ‘/admin/’ entonces la variable IS_ADMIN existirá«. Luego, se indica el alcance de la autenticación: Primero se indica que, por default, a todos los usuarios se les pedirá acceso con usuario y contraseña («Deny from all»), luego se indica que el acceso puede ser parcial y se admite que haya grupos que no necesiten ingresar usuario y contraseña («Satisfy any»), y finalmente se establece la condición para ese grupo, que es «permitir el acceso si la variable IS_ADMIN no existe«. Por último, se indica qué recurso se requiere para acceder, que en este caso será un par usuario y contraseña válido de los que están dentro del archivo «/var/www/htpasswd» («Require valid-user»).

Es decir, concede el acceso para cualquier URI que no comience con «/admin/», que se supone que es la zona de administración y debe estar protegida.

Unificando directivas y simplificando el proceso: Apache Expressions parser

Al existir distintas directivas y módulos con propósitos similares o que utilizan expresiones regulares de formas tan variadas, en Apache 2.4 se introdujo un nuevo mecanismo para intentar unificar todo en una única variante: el analizador sintáctico de expresiones de Apache o Apache Expressions parser. De esta forma, se establece una directiva única para resolver todo lo que antes requería del uso de múltiples directivas o módulos, o de sintaxis distintas para cada uno.

Equivalencia con SetEnvIf

Como ejemplo, escribiremos la equivalencia de la expresión del ejemplo anterior.

AuthType Basic
AuthName "Protegido por contraseña"
AuthUserFile /var/www/htpasswd

Require expr %{REQUEST_URI} !~ m#^/admin/#

Require valid-user

Explicación: El operador !~ (lista de operadores) significa «la cadena no coincide con la expresión regular». La expresión regular utilizada es la misma que la anterior: ^/admin/, sólo que está encerrada entre m#<regex># como alternativa al uso estándar de /<regex>/ para permitir el uso del carácter «/» como parte de la expresión regular. De otra forma, habría que haberla escapeado: /^\/admin\//, haciendo que la regex se torne más difícil de leer e interpretar (para un humano).

En síntesis, lo que nos dice esta instrucción es «permitir el acceso si la URI no comienza con /admin/«. Si se lee todo el bloque, sería algo como «pedir usuario y contraseña para todos los usuarios en todos los recursos, excepto para los recursos cuya URI no comience con /admin/».

Apuntando no sólo a la URL

Apache Expressions - WordPress action:postpass | © LucianoFantuzzi.com, 2020
Apache Expressions – WordPress / wp-login.php / HTTP GET action:postpass

Dada la flexibilidad de la directiva, se pueden utilizar muchas otras variables para comparar con la expresión regular. Por ejemplo, también es posible apuntar directo contra parámetros específicos del query string:

<Files wp-login.php>
    AuthType Basic
    ...
    Require expr %{QUERY_STRING} =~ /action=postpass/
    ...
</Files>

Este es un ejemplo real de la expresión que utilizo en WordPress para permitirle al usuario la autenticación de páginas protegidas con contraseña (feature integrado de WordPress), a través del archivo wp-login.php, que es el archivo que protejo con autenticación básica para evitar ataques contra la autenticación del admin.

Referencias y Links de interés

Deja un comentario