Usar 3scale con varnish asíncronamente

Si tienes una API, seguramente conozcas 3scale, un servicio de gestión de APIS que incluye la generación de claves, relación con tus clientes, facturación, control de acceso y estadísticas. Es gratuito hasta 150.000 hits al día, y muy útil.

Además tienen un módulo de varnish para poder hacer transparente la autenticación en tu aplicación. El problema es que ese módulo no es muy estable, es complejo de instalar y más o menos síncrono (aunque cachea las autenticaciones y envía post-conexión las estadísticas), con lo que complica tu sistema y puede fácilmente causarte pesadillas.

Antes de usar 3scale te recomiendo que te mires también Mashape. 😉

En mi caso no necesito un control estricto de mis usuarios (de hecho, lo único que prácticamente necesito es analítica de uso para no pasarme de los 150K hits/día), así que pensé en una manera asíncrona de gestionarlo y opté por la gestión del log de varnish y su procesamiento posterior mediante cron.

El log de varnish

Lo primero que hay que hacer es activar el log de varnish. varnishncsa es una utilidad que lee los logs de la memoria compartida de varnishd y los presenta en un formato Apache / NCSA “combined”.

Como el log de varnish genera *MUCHA* información y yo tengo varios virtualhosts en mi servidor, sólo guardaré las llamadas a la api mediante el host (directiva -m de varnishncsa)

Añadimos esta línea a /etc/rc.local para que el comando se ejecute cada vez que arranquemos el servidor.

varnishncsa -a -w /var/log/varnish/api.log -D -P /var/run/varnishncsa.pid -c -m "RxHeader:Host: api.example.com"

Para probar, podemos ejecutar el comando ahora mismo. Veremos que al poco rato (dependiendo de el uso que tenga nuestra api) se va llenando el archivo /var/log/varnish/api.log

Procesando el log

Una vez tenemos el log, lo procesamos. Yo usaré PHP porque es con lo que más rápido programo. Nos bajaremos la API de PHP que los chicos de 3scale tienen disponible y la usaremos para enviar las estadísticas. Lo haremos agrupando primero todos los hits de cada app_id y así ahorramos muchas conexiones con 3scale a la hora de enviar el report.

mkdir /root/3scale
cd /root/3scale
wget https://github.com/3scale/3scale_ws_api_for_php/archive/master.zip -O master.zip
unzip master.zip
vim process_varnish_logs.php

Y pegamos este contenido en el archivo process_varnish_logs.php:

 $hits) {
        $report[] = array('app_id' => $app_id, 'usage' => array('hits' => $hits));
}
require_once('3scale_ws_api_for_php-master/lib/ThreeScaleClient.php');
$client = new ThreeScaleClient("__Y0URS3CR3TK3Y__");
$response = $client->report($report);

$handle_write = fopen($filename, "r+");
if ($handle_write !== false) {
    ftruncate($handle_write, 0);
    fclose($handle_write);
}

En el administrador de 3scale encontrarás tu __Y0URS3CR3TK3Y__.

Podemos probarlo (ojo que las últimas 5 líneas lo que hacen es borrar el archivo de logs! Haz una copia primero de ese archivo antes de probarlo) ejecutando:

php process_varnish_logs.php

Para instalarlo como un cronañadimos esta línea para ejecutarlo cada 10 minutos (en mi caso es de sobras). Para editar los crons, usa crontab -e

# Send api usage statistics to 3scale
*/10 * * * * /usr/bin/php /root/3scale/process_varnish_logs.php > /dev/null

El control de accesos

Es un tema que aún no he resuelto, habría que generar un archivo de texto con los id y claves de los usuarios de la api que son correctos, quitando de él los que se pasen de los límites. Todo eso es fácil desde el process_varnish_logs.php usando la API de 3scale.

La dificultad la veo en leer desde el VCL de varnish ese archivo y comparar los strings enviados en el req.url. De momento he hecho esto:

import std;

sub vcl_recv {
  set req.http.X-app_id   = regsub(req.url,".*[&?]app_id=([a-f0-9]{8}).*","\1");
  set req.http.X-app_key  = regsub(req.url,".*[&?]app_key=([a-f0-9]+).*","\1");
  if(req.http.X-app_id) {
    set req.http.X-auth_apps = std.fileread("/etc/varnish/.auth_apps");
    std.log("auth_apps: "   + req.http.X-auth_apps);
    std.log("app_id: "   + req.http.X-app_id);
    std.log("app_key: "  + req.http.X-app_key);
  }
}

¿Alguna ayuda? Lo que me queda es comparar los IDs y KEYs con los del archivo .auth_apps.