Using Memcached + MySQL + Inline C ( Varnish Cache ) to authenticate basic keys ( Restfull API ).

Comments Off

Posted on 5th September 2011 by administrador in Linux

, , , , , , , ,

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.

UA-10170569-1