This commit is contained in:
Mark Riddoch
2015-02-13 08:48:30 +00:00
2 changed files with 130 additions and 94 deletions

View File

@ -1,4 +1,4 @@
[Search page for MaxScale Documentation]((http://mariadb-corporation.github.io/MaxScale/Search/)
[Search page for MaxScale Documentation](http://mariadb-corporation.github.io/MaxScale/Search/)
# Contents

View File

@ -18,39 +18,48 @@
/**
* @file fwfilter.c
* Firewall Filter
* @author Markus Mäkelä
* @date 13.2.2015
* @version 1.0.0
* @copyright GPLv2
* @section secDesc Firewall Filter
*
* A filter that acts as a firewall, denying queries that do not meet a set of rules.
*
* Filter configuration parameters:
*
*@code{.unparsed}
* rules=<path to file> Location of the rule file
*
*@endcode
* Rules are defined in a separate rule file that lists all the rules and the users to whom the rules are applied.
* Rules follow a simple syntax that denies the queries that meet the requirements of the rules.
* For example, to define a rule denying users from accessing the column 'salary' between
* the times 15:00 and 17:00, the following rule is to be configured into the configuration file:
*
*@code{.unparsed}
* rule block_salary deny columns salary at_times 15:00:00-17:00:00
*
*@endcode
* The users are matched by username and network address. Wildcard values can be provided by using the '%' character.
* For example, to apply this rule to users John, connecting from any address
* that starts with the octets 198.168.%, and Jane, connecting from the address 192.168.0.1:
*
*@code{.unparsed}
* users John@192.168.% Jane@192.168.0.1 match any rules block_salary
*@endcode
*
*
* The 'match' keyword controls the way rules are matched. If it is set to 'any' the first active rule that is triggered will cause the query to be denied.
* The 'match' keyword controls the way rules are matched. If it is set to
* 'any' the first active rule that is triggered will cause the query to be denied.
* If it is set to 'all' all the active rules need to match before the query is denied.
*
* Rule syntax
*
* @subsection secRule Rule syntax
* This is the syntax used when defining rules.
*@code{.unparsed}
* rule NAME deny [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF | no_where_clause] [at_times VALUE...] [on_queries [select|update|insert|delete]]
*
* User syntax
*
*@endcode
* @subsection secUser User syntax
* This is the syntax used when linking users to rules. It takes one or more
* combinations of username and network, either the value any or all,
* depending on how you want to match the rules, and one or more rule names.
*@code{.unparsed}
* users NAME ... match [any|all] rules RULE ...
*
*@endcode
*/
#include <my_config.h>
#include <stdint.h>
@ -109,13 +118,13 @@ static FILTER_OBJECT MyObject = {
* Rule types
*/
typedef enum {
RT_UNDEFINED = 0x00,
RT_COLUMN,
RT_THROTTLE,
RT_PERMISSION,
RT_WILDCARD,
RT_REGEX,
RT_CLAUSE
RT_UNDEFINED = 0x00, /*< Undefined rule */
RT_COLUMN, /*< Column name rule*/
RT_THROTTLE, /*< Query speed rule */
RT_PERMISSION, /*< Simple denying rule */
RT_WILDCARD, /*< Wildcard denial rule */
RT_REGEX, /*< Regex matching rule */
RT_CLAUSE /*< WHERE-clause requirement rule */
}ruletype_t;
const char* rule_names[] = {
@ -133,25 +142,31 @@ const char* rule_names[] = {
* Linked list of strings.
*/
typedef struct strlink_t{
struct strlink_t *next;
char* value;
struct strlink_t *next; /*< Next node in the list */
char* value; /*< Value of the current node */
}STRLINK;
/**
* A structure defining a range of time
*/
typedef struct timerange_t{
struct timerange_t* next;
struct tm start;
struct tm end;
struct timerange_t* next; /*< Next node in the list */
struct tm start; /*< Start of the time range */
struct tm end; /*< End of the time range */
}TIMERANGE;
/**
* Query speed measurement and limitation structure
*/
typedef struct queryspeed_t{
time_t first_query;
time_t triggered;
double period;
double cooldown;
int count;
int limit;
long id;
struct queryspeed_t* next;
time_t first_query; /*< Time when the first query occurred */
time_t triggered; /*< Time when the limit was exceeded */
double period; /*< Measurement interval in seconds */
double cooldown;/*< Time the user is denied access for */
int count; /*< Number of queries done */
int limit; /*< Maximum number of queries */
long id; /*< Unique id of the rule */
struct queryspeed_t* next; /*< Next node in the list */
}QUERYSPEED;
@ -162,65 +177,70 @@ typedef struct queryspeed_t{
* This allows to match an arbitrary set of rules against a user.
*/
typedef struct rule_t{
void* data;
char* name;
ruletype_t type;
skygw_query_op_t on_queries;
bool allow;
int times_matched;
TIMERANGE* active;
void* data; /*< Actual implementation of the rule */
char* name; /*< Name of the rule */
ruletype_t type;/*< Type of the rule */
skygw_query_op_t on_queries;/*< Types of queries to inspect */
bool allow;/*< Allow or deny the query if this rule matches */
int times_matched;/*< Number of times this rule has been matched */
TIMERANGE* active;/*< List of times when this rule is active */
}RULE;
/**
* Linked list of pointers to a global pool of RULE structs
*/
typedef struct rulelist_t{
RULE* rule;
struct rulelist_t* next;
RULE* rule; /*< The rule structure */
struct rulelist_t* next;/*< Next node in the list */
}RULELIST;
typedef struct user_t{
char* name;
SPINLOCK* lock;
QUERYSPEED* qs_limit;
RULELIST* rules_or;
RULELIST* rules_and;
char* name;/*< Name of the user */
SPINLOCK* lock;/*< User spinlock */
QUERYSPEED* qs_limit;/*< The query speed structure unique to this user */
RULELIST* rules_or;/*< If any of these rules match the action is triggered */
RULELIST* rules_and;/*< All of these rules must match for the action to trigger */
}USER;
/**
* Linked list of IP adresses and subnet masks
*/
typedef struct iprange_t{
struct iprange_t* next;
uint32_t ip;
uint32_t mask;
struct iprange_t* next;/*< Next node in the list */
uint32_t ip;/*< IP address */
uint32_t mask;/*< Network mask */
}IPRANGE;
/**
* The Firewall filter instance.
*/
typedef struct {
HASHTABLE* htable;
RULELIST* rules;
STRLINK* userstrings;
bool def_op;
SPINLOCK* lock;
long idgen; /**UID generator*/
HASHTABLE* htable; /*< User hashtable */
RULELIST* rules;/*< List of all the rules */
STRLINK* userstrings;/*< Temporary list of raw strings of users */
bool def_op;/*< Default operation mode, defaults to deny */
SPINLOCK* lock;/*< Instance spinlock */
long idgen; /*< UID generator */
} FW_INSTANCE;
/**
* The session structure for Firewall filter.
*/
typedef struct {
SESSION* session;
char* errmsg;
DOWNSTREAM down;
UPSTREAM up;
SESSION* session;/*< Client session structure */
char* errmsg;/*< Rule specific error message */
DOWNSTREAM down;/*< Next object in the downstream chain */
UPSTREAM up;/*< Next object in the upstream chain */
} FW_SESSION;
static int hashkeyfun(void* key);
static int hashcmpfun (void *, void *);
/**
* Hashtable key hashing function. Uses a simple string hashing algorithm.
* @param key Key to hash
* @return The hash value of the key
*/
static int hashkeyfun(
void* key)
{
@ -232,9 +252,16 @@ static int hashkeyfun(
while((c = *ptr++)){
hash = c + (hash << 6) + (hash << 16) - hash;
}
return (int)hash > 0 ? hash : -hash;
return hash;
}
/**
* Hashtable entry comparison function. Does a string matching operation on the
* two keys. This function assumes the values are pointers to null-terminated
* character arrays.
* @param v1 The first key
* @param v2 The second key
* @return Zero if the values are equal. Non-zero in other cases.
*/
static int hashcmpfun(
void* v1,
void* v2)
@ -245,19 +272,6 @@ static int hashcmpfun(
return strcmp(i1,i2);
}
static void* hstrdup(void* fval)
{
char* str = (char*)fval;
return strdup(str);
}
static void* hstrfree(void* fval)
{
free (fval);
return NULL;
}
void* rlistdup(void* fval)
{
@ -588,7 +602,7 @@ RULE* find_rule(char* tok, FW_INSTANCE* instance)
}
rlist = rlist->next;
}
skygw_log_write(LOGFILE_ERROR, "fwfilter: Rule not found: %s",tok);
skygw_log_write(LOGFILE_ERROR, "Error : Rule not found: %s",tok);
return NULL;
}
@ -602,6 +616,10 @@ void add_users(char* rule, FW_INSTANCE* instance)
assert(rule != NULL && instance != NULL);
STRLINK* link = calloc(1,sizeof(STRLINK));
if(link == NULL){
skygw_log_write(LOGFILE_ERROR,"Error : Memory allocation failed");
return;
}
link->next = instance->userstrings;
link->value = strdup(rule);
instance->userstrings = link;
@ -838,20 +856,21 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
regex_t *re;
char* start, *str;
tok = strtok(NULL," ");
char delim = '\'';
while(*tok == '\'' || *tok == '"'){
delim = *tok;
tok++;
}
start = tok;
while(isspace(*tok) || *tok == '\'' || *tok == '"'){
while(isspace(*tok) || *tok == delim){
tok++;
}
while(true){
if((*tok == '\'' || *tok == '"') && !escaped){
if((*tok == delim) && !escaped){
break;
}
escaped = (*tok == '\\');
@ -896,11 +915,23 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
spinlock_release(instance->lock);
tok = strtok(NULL," ");
if(tok == NULL){
free(qs);
goto retblock;
}
qs->limit = atoi(tok);
tok = strtok(NULL," ");
if(tok == NULL){
free(qs);
goto retblock;
}
qs->period = atof(tok);
tok = strtok(NULL," ");
if(tok == NULL){
free(qs);
goto retblock;
}
qs->cooldown = atof(tok);
ruledef->type = RT_THROTTLE;
ruledef->data = (void*)qs;
@ -958,13 +989,13 @@ createInstance(char **options, FILTER_PARAMETER **params)
spinlock_init(my_instance->lock);
if((ht = hashtable_alloc(7, hashkeyfun, hashcmpfun)) == NULL){
if((ht = hashtable_alloc(100, hashkeyfun, hashcmpfun)) == NULL){
skygw_log_write(LOGFILE_ERROR, "Unable to allocate hashtable.");
free(my_instance);
return NULL;
}
hashtable_memory_fns(ht,hstrdup,NULL,hstrfree,hrulefree);
hashtable_memory_fns(ht,(HASHMEMORYFN)strdup,NULL,(HASHMEMORYFN)free,hrulefree);
my_instance->htable = ht;
my_instance->def_op = true;
@ -979,18 +1010,22 @@ createInstance(char **options, FILTER_PARAMETER **params)
}
}
if(filename == NULL)
{
skygw_log_write(LOGFILE_ERROR, "Unable to find rule file for firewall filter.");
skygw_log_write(LOGFILE_ERROR, "Unable to find rule file for firewall filter. Please provide the path with"
" rules=<path to file>");
hashtable_free(my_instance->htable);
free(my_instance);
return NULL;
}
if((file = fopen(filename,"rb")) == NULL ){
skygw_log_write(LOGFILE_ERROR, "Error while opening rule file for firewall filter.");
free(my_instance);
free(filename);
return NULL;
hashtable_free(my_instance->htable);
free(my_instance);
free(filename);
return NULL;
}
free(filename);
@ -1002,6 +1037,7 @@ createInstance(char **options, FILTER_PARAMETER **params)
if(ferror(file)){
skygw_log_write(LOGFILE_ERROR, "Error while reading rule file for firewall filter.");
fclose(file);
hashtable_free(my_instance->htable);
free(my_instance);
return NULL;
}
@ -1037,9 +1073,7 @@ createInstance(char **options, FILTER_PARAMETER **params)
/**
* Associate a new session with this instance of the filter and opens
* a connection to the server and prepares the exchange and the queue for use.
*
* Associate a new session with this instance of the filter.
*
* @param instance The filter instance data
* @param session The session itself
@ -1370,15 +1404,17 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue
spinlock_acquire(user->lock);
queryspeed = user->qs_limit;
spinlock_release(user->lock);
while(queryspeed){
if(queryspeed->id == rule_qs->id){
break;
}
queryspeed = queryspeed->next;
}
if(queryspeed == NULL){
/**No match found*/
@ -1427,7 +1463,7 @@ bool rule_matches(FW_INSTANCE* my_instance, FW_SESSION* my_session, GWBUF *queue
{
queryspeed->first_query = time_now;
}
spinlock_release(user->lock);
break;
case RT_CLAUSE: