Introducción a proxy inverso

Un proxy inverso es un tipo de servidor proxy que recupera recursos en nombre de un cliente desde uno o más servidores. Por lo tanto el cliente hace la petición al puerto 80 del proxy (o al puerto 443 si usamos https), y éste es el que hace la petición al servidor web que normalmente está en una red interna no accesible desde el cliente.

proxy

Apache2 como proxy inverso

Apache2.4 puede funcionar como proxy inverso usando el módulo proxy junto a otros módulos, por ejemplo:

  • proxy_http: Para trabajar con el protocolo HTTP.
  • proxy_ftp: Para trabajar con el protocolo FTP.
  • proxy_html: Permite reescribir los enlaces HTML en el espacio de direcciones de un proxy.

Ejemplo de utilización de proxy inverso

Vamos a usar los ficheros del directorio proxy_inverso del repositorio taller_http para crear la infraestructura con vagrant. Se va a crear un servidor interno (no accesible desde el cliente) con una dirección privada, con el nombre de interno.example.org. Tenemos un servidor que va a funcionar de proxy, llamado proxy.example.org con dos interfaces de red: una pública conectada a la red donde se encuentra el cliente (será la red de mantenimiento), y otra interna conectada a la red donde se encuentra el servidor interno.

Vagrant ha configurado los siguiente:

  • En el servidor proxy ha instalado apache2, ha configurado el router-nat y ha escrito en la resolución estática el nombre del servidor interno.
  • En el servidor web interno se ha instalado apache2, se ha cambiado la ruta por defecto para salir por el proxy, se ha copia una página web, se ha configurado una redirección con un fichero .htaccess y por último se ha cambiado la configuración de apache2 para permitir ficheros .htaccess.

En nuestro servidor interno hemos creado un virtual host para servir una página estática, index.html, con este contenido:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Probando proxy inverso con Apache2</title>
</head>
<body>
        <h1>Probando proxy inverso con Apache2</h1>
        <img src="img.jpg"/>
        <img src="/img.jpg"/>
        <br/>
        <br/>
        <a href="directorio">Redirección</a>
        <br/><br/>
        <a href="http://10.0.0.2/carpeta/index.html">Enlace tipo 1</a><br/>
        <a href="/carpeta/index.html">Enlace tipo 2</a><br/>
        <a href="carpeta/index.html">Enlace tipo 3</a>
</body>

Debemos recordar que desde nuestro cliente no tenemos acceso al servidor web interno. No vamos a usar la IP de la red de mantenimiento para acceder.

Por lo tanto necesitamos un proxy inverso:

Configuración del proxy inverso

En el servidor proxy vamos a activar la funcionalidad de proxy inverso. Por lo tanto, para empezar, vamos activar los módulos que necesitamos:

# a2enmod proxy proxy_http

Creamos un virtualhost para servir el contenido ofrecido con el nombre del host proxy.example.org.

Vamos a utilizar la directiva ProxyPass en el fichero de configuración del virtual host, de la siguiente forma:

ProxyPass "/" "http://interno.example.org/"

También lo podemos configurar de forma similar con:

<Location "/">
    ProxyPass "http://interno.example.org/"
</Location>

Evidentemente debe funcionar la resolución de nombre para que el proxy pueda acceder al servidor interno y para que podamos acceder al proxy. En el fichero /etc/hosts del cliente indica la resolución estática usando la IP de la red de mantenimiento del proxy:

172.22.121.XX	proxy.example.org

De esta manera al acceder desde el cliente la URL http://proxy.example.org/ se mostraría la página que se encuentra en el servidor interno.

proxy

Hay que tener en cuenta que las páginas HTML tienen que estar escritas de forma adecuada para permitir ser servidas por un proxy. Veamos los errores que se han presentado:

  1. El enlace de tipo 1, nunca va a funcionar porque como observamos en el código HTML hace referencia a la IP interna del servidor web interno. No podemos usar referencias absolutas a la IP de un servidor web en los enlaces.
  2. La redirección no funciona. Veamos a continuación la solución al problema de las redirecciones.

El problema de las redirecciones

Cuando creamos una redirección en un servidor web y el cliente intenta acceder al recurso, el servidor manda una respuesta con código de estado 301 o 302, e indica la URL de la nueva ubicación del recurso en una cabecera HTTP llamada Location.

Si hemos configurado una redirección en el servidor interno, cuando se accede al recurso a través del proxy, la redirección se realiza pero la cabecera Location viene referencia la dirección del servidor interno, por lo que el cliente es incapaz de acceder a la nueva ubicación. Al acceder a http://proxy.example.org/web/directorio se produce una redirección pero como vemos la nueva url hace referencia al servidor interno por lo que no funciona:

proxy

Para solucionarlo utilizamos la directiva ProxyPassReverse que se encarga de reescribir la URL de la cabecera Location.

La configuración quedaría:

ProxyPass "/" "http://interno.example.org/"
ProxyPassReverse "/" "http://interno.example.org/"

O de esta otra forma:

<Location "/">
    ProxyPass "http://interno.example.org/"
    ProxyPassReverse "http://interno.example.org/"
</Location>

Por lo que ya podemos hacer la redirección de forma correcta:

proxy

Proxy inverso usando rutas

Si tuviéramos varios servidores web internos o varias páginas o aplicaciones web a las que tuviéramos que acceder a través del proxy, tendríamos dos opciones:

  1. Usar un VirtualHost para cada una de las aplicaciones web y configurarlos como hemos explicado anteriormente.
  2. Tener un sólo VirtualHost y configurar distintas rutas para acceder a cada una de las aplicaciones.

Vamos este segundo caso con más detenimiento. Vamos a imaginar que queremos acceder a la página web del servidor interno con la URL http://proxy.example.org/web/. En este caso la configuración del proxy cambiaría de la siguiente manera:

ProxyPass "/web/" "http://interno.example.org/"
ProxyPassReverse "/web/" "http://interno.example.org/"

O de esta otra forma:

<Location "/web/">
    ProxyPass "http://interno.example.org/"
    ProxyPassReverse "http://interno.example.org/"
</Location>

De esta manera al acceder desde el cliente la URL http://proxy.example.org/web/ se mostraría la página que se encuentra en el servidor interno.

proxy

Veamos ahora los errores que se han presentado:

  1. Como vemos una imagen no se ha cargado. La segunda imagen utiliza una path absoluto, es decir, se espera que la imagen esté en la raíz del DocumentRoot y en este caso estamos accediendo a la ruta /web/: no existe una imagen en la raíz.
  2. El enlace de tipo 1 no funciona por la misma razón que vimos anteriormente, pero ahora tampoco funciona el enlace de tipo 2, porque de forma similar a la imagen que no se ve, hace referencia a la raíz del DocumentRoot, y en este caso, como ya hemos dicho, estamos accediendo usando la ruta /web/.

Terminamos diciendo que al crear las páginas HTML hay que evitar rutas absolutas donde aparezcan direcciones IP, y también referencias a la raíz del sitio web.

nginx como proxy inverso

Para que nginx funcione como proxy inverso en nuestro ejemplo anterior, hay que poner está configuración:

location / {
    proxy_pass http://interno.example.org/;
    include proxy_params;
}

Todas las peticiones HTTP vienen con cabeceras, que contienen información sobre el petición HTTP. En el fichero /etc/nginx/proxyparams se definen parámetro para reenviar las cabeceras más importantes. El contenido del fichero es el siguiente:

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

Para pasar las cabeceras provinientes de la petición del cliente al servidor interno se utiliza la directiva proxy_set_header.

Estas son las cabeceras reenviadas por nginx y las variables en las que almacena los datos:

  • Host: Esta cabecera contiene el host original solicitado por el cliente, que es el dominio del sitio web y el puerto. Nginx guarda esto en la variable $http_host.
  • X-Forwarded-For: Esta cabecera contiene la dirección IP del cliente que envió la solicitud original. También guarda la lista de IP de los proxy inversos intermedios. Nginx guarda esto en la variable $proxy_add_x_forwarded_for.
  • X-Real-IP: Esta cabecera siempre contiene una única dirección IP que pertenece al cliente remoto. Nginx guarda ese valor en la variable $remote_addr.
  • X-Forwarded-Proto: Esta cabecera contiene el protocolo utilizado por el cliente original para conectarse, ya sea HTTP o HTTPS. Nginx lo guarda en la variable $scheme.

En la configuración para servir algunas aplicaciones web es necesario el reenvío de más cabeceras.

Por último si queremos ofrecer la aplicación en una ruta concreta, la configuración sería:

location /web/ {
    proxy_pass http://interno.example.org/;
    include proxy_params;
}