Tag: api
Combinando Memcached, MySQL e codigo C para autenticar usuários de uma API RESTFull.
by moutinho on Sep.05, 2011, under Varnish
Depois de algum tempo pesquisando (e principalmente inspirado pelo post do Dr. Carter), resolvi implementar uma autenticação de clientes API (RESTfull) que precisava escalar.
O intuito deste post é compartilhar o pedaço de codigo (inline C) hoje responsável pela autenticação dos clientes e não como configurar o Varnish Cache para escalar websites.
O codigo consiste em pegar a chave de autenticação enviada através de um Header HTTP especifico e verificar sua autenticidade, combinando com a contagem de requests que o cliente possui permissão (tudo isso evitando ao maximo uma chamada ao banco de dados).
/* Import these 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
/* Place the code inside 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 "API Key + Client IP" to count the requests.
Adapt the key used to suit 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 the connection to memcached was succesfull we'll decrement the value of 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 (probably the key is not in cache) we need to authenticate the key on database.
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 api_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 API Key was found in database we collect the request limit value and store it.
I 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 in 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
para o carregamento das bibliotecas necessárias, você precisará adicionar ao script de inicialização do Varnish (/etc/sysconfig/varnish) os seguintes parametros:
-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’
obs1: você vai precisar ter instalado em seu servidor o mysql-client & mysql-devel package assim como a libmemcached.
A thank you note to the varnish mailing list which assisted me thru the entire process.