After reading Dr. Carter post about blocking web scraping with Varnish Cache inline C code…
I’ve had the ideia do authenticate user of a Restfull API (basic http auth) using the same tools + mysql.
I won’t cover the varnish cache configuration… just the inline C code I’ve used to do the job.
/* Import this modules in the begining of your VCL code */
C{
#include <string.h>
#include </usr/local/include/libmemcached/memcached.h>
#include <syslog.h>
#include </usr/include/mysql/mysql.h>
#include <stdio.h>
}C
/* Insert the code on your vcl_recv wherever you want (as longs as your requests passes thru the code) */
C{
memcached_server_st *servers = NULL;
memcached_st *memc;
memcached_return_t rc;
/*
I use a combination of "KEY_IP" to count the requests.
So change the next lines to adapt the key used in memcached to your needs.
*/
char key[50];
strcat(key, VRT_GetHdr(sp, HDR_REQ, "\012X-API-Key:"));
strcat(key, "_");
strcat(key, VRT_IP_string(sp, VRT_r_client_ip(sp)));
memc= memcached_create(NULL);
servers= memcached_server_list_append(servers, "localhost", 11211, &rc);
rc= memcached_server_push(memc, servers);
syslog(LOG_INFO, "Memcached connection = %s",memcached_strerror(memc, rc));
/* If connection to memcached was succesfull we'll try to decrement this key. */
if (rc == MEMCACHED_SUCCESS) {
rc = memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, 10);
uint64_t intval;
rc= memcached_decrement(memc, key, strlen(key), (uint64_t)1, &intval);
if (rc != MEMCACHED_SUCCESS) {
/*
If decrement has failed we need to authenticate the key on MySQL (which means that the key has not been verified before).
Remember to create a view (for safety reasons the mysql user is only privileged to use the created view) and to add an index on your table (for a fast read).
*/
syslog(LOG_INFO, "Key found in Memcached ? = %s",memcached_strerror(memc, rc));
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
char rows;
char query[200];
sprintf(query, "select request-limit from view where key = '%s'" ,VRT_GetHdr(sp, HDR_REQ, "\012X-API-Key:"));
conn = mysql_init(NULL);
if (mysql_real_connect (conn,"localhost","user","password","database_name",0,NULL,0) != NULL){
mysql_real_query(conn,query,strlen(query));
res = mysql_store_result(conn);
row = mysql_fetch_row(res);
if (row) {
syslog(LOG_INFO, "Result != NULL");
char *limit = row[0];
syslog(LOG_INFO,"Key found in MySQL with %s requests total.",limit);
rc= memcached_set(memc, key, strlen(key), limit, strlen(limit), (time_t)3600, (uint32_t)0);
syslog(LOG_INFO, "Key %s stored in Memcached ? = %s",key,memcached_strerror(memc, rc));
/*
If the Key has been found on database we collect the request limit and store it together with the key.
We also set a 1 hour limit for this memcached object (3600) and log the response code from memcached.
*/
} else {
syslog(LOG_INFO, "Key: %s not found in MySQL.",VRT_GetHdr(sp, HDR_REQ, "\012X-API-Key:"));
VRT_error(sp, 401, "Unauthorized.");
VRT_done(sp, VCL_RET_ERROR);
/* The provided key was not found on the database. Return a "401 Unauthorized" code. */
}
} else {
syslog(LOG_INFO,"MySQL: Error %u (%s)",mysql_errno (conn), mysql_error (conn));
VRT_error(sp, 500, "Internal server error.");
VRT_done(sp, VCL_RET_ERROR);
/* In case of a database connection problem we return a "500 Internal server error" code. */
}
mysql_free_result(res);
mysql_close(conn);
}
else {
syslog(LOG_INFO, "Key found in Memcached ? = %s",memcached_strerror(memc, rc));
syslog(LOG_INFO, "Decrement key %s in Memcached ? = %s",key,memcached_strerror(memc, rc));
if (intval < 1) {
syslog(LOG_INFO, "Key %s limit exceeded. Client Ip = %s",VRT_GetHdr(sp, HDR_REQ, "\012X-API-Key:"),VRT_IP_string(sp, VRT_r_client_ip(sp)));
VRT_error(sp, 403, "Forbidden");
VRT_done(sp, VCL_RET_ERROR);
/* If the decremented result from memcached is lower than "1" we return a "403 Forbidden" code. Client requests maxed-out. */
}
}
}
memcached_free(memc);
memcached_server_list_free(servers);
}C
and to my varnish daemon startup I’ve added:
-p ‘cc_command=exec cc -fpic -shared -Wl,-x -L/usr/local/include/libmemcached/memcached.h -L/usr/lib64/mysql -lmysqlclient -lmemcached -o %o %s’
ps1: you’ll need mysql-client & mysql-devel package and also libmemcached.
ps2: A thank you note to the varnish mailing list which assisted me thru the entire process.