From f233bfab661e9170516bb495b60e59e900fcfc80 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 17 Nov 2014 14:52:26 +0200 Subject: [PATCH] Updated test harness and added preliminary firewall filter tests --- server/modules/filter/fwfilter.c | 60 ++++---- server/modules/filter/test/CMakeLists.txt | 4 +- server/modules/filter/test/fwtest.cnf | 159 ++++++++++++++++++++ server/modules/filter/test/fwtest.cnf.in | 159 ++++++++++++++++++++ server/modules/filter/test/fwtest.input | 7 + server/modules/filter/test/harness.h | 14 +- server/modules/filter/test/harness_common.c | 73 +++++++-- server/modules/filter/test/harness_ui.c | 4 +- server/modules/filter/test/harness_util.c | 6 +- server/modules/filter/test/rules | 3 + 10 files changed, 442 insertions(+), 47 deletions(-) create mode 100644 server/modules/filter/test/fwtest.cnf create mode 100644 server/modules/filter/test/fwtest.cnf.in create mode 100644 server/modules/filter/test/fwtest.input create mode 100644 server/modules/filter/test/rules diff --git a/server/modules/filter/fwfilter.c b/server/modules/filter/fwfilter.c index e3a3fe720..2aadec959 100644 --- a/server/modules/filter/fwfilter.c +++ b/server/modules/filter/fwfilter.c @@ -20,25 +20,37 @@ * @file fwfilter.c * Firewall Filter * - * A filter that acts as a firewall, denying queries that do not meet a set requirements. + * A filter that acts as a firewall, denying queries that do not meet a set of rules. * - * This filter uses "rules" to define the blcking parameters. Write the rules to a separate file and - * set the path to the file: + * Filter configuration parameters: * - * rules= + * rules= Location of the rule file * - * - * For example, to define a rule denying users from accessing the column 'salary' between 15:00 and 17:00, the following is needed in the configuration file: + * 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: * * rule block_salary deny columns salary at_times 15:00:00-17:00:00 * - * To apply this rule to users John, connecting from any address, and Jane, connecting from the address 192.168.0.1, use the following: + * 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: * - * users John@% Jane@192.168.0.1 match any rules block_salary + * users John@192.168.% Jane@192.168.0.1 match any rules block_salary * - * Rule syntax TODO: update the documentation * - * rule NAME deny|allow [wildcard | columns VALUE ... | regex REGEX | limit_queries COUNT TIMEPERIOD HOLDOFF|no_where_clause [select|insert|delete|update]] [at_times VALUE...] + * 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 + * + * 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 + * + * users NAME ... match [any|all] rules RULE ... + * */ #include #include @@ -91,20 +103,7 @@ static FILTER_OBJECT MyObject = { diagnostic, }; -#define QUERY_TYPES 5 -/** - * Query types - */ -/*typedef enum{ - NONE = 0, - ALL = (1), - SELECT = (1<<1), - INSERT = (1<<2), - UPDATE = (1<<3), - DELETE = (1<<4) -}querytype_t; -*/ /** * Rule types */ @@ -259,12 +258,6 @@ void* rlistdup(void* fval) return (void*)rule; } -/* - static void* hruledup(void* fval) - { - return fval; - }*/ - static void* hrulefree(void* fval) { @@ -291,6 +284,7 @@ static void* hrulefree(void* fval) * Replace all non-essential characters with whitespace from a null-terminated string. * This function modifies the passed string. * @param str String to purify + * @return Pointer to the modified string */ char* strip_tags(char* str) { @@ -354,7 +348,12 @@ char* next_ip_class(char* str) return str; } - +/** + * Parses the strign for the types of queries this rule should be applied to. + * @param str String to parse + * @param rule Poiter to a rule + * @return True if the string was parses successfully, false if an error occurred + */ bool parse_querytypes(char* str,RULE* rule) { char buffer[512]; @@ -562,6 +561,7 @@ RULE* find_rule(char* tok, FW_INSTANCE* instance) skygw_log_write(LOGFILE_ERROR, "fwfilter: Rule not found: %s",tok); return NULL; } + /** * Adds the given rule string to the list of strings to be parsed for users. * @param rule The rule string, assumed to be null-terminated diff --git a/server/modules/filter/test/CMakeLists.txt b/server/modules/filter/test/CMakeLists.txt index 1813a5a26..5a94170c8 100644 --- a/server/modules/filter/test/CMakeLists.txt +++ b/server/modules/filter/test/CMakeLists.txt @@ -10,4 +10,6 @@ add_executable(harness_ui harness_ui.c harness_common.c) add_executable(harness harness_util.c harness_common.c ${CORE}) target_link_libraries(harness_ui fullcore log_manager utils) target_link_libraries(harness fullcore) -add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 && diff ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output;exit $?") \ No newline at end of file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf.in ${CMAKE_CURRENT_BINARY_DIR}/fwtest.cnf @ONLY) +add_test(TestHintfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.input -o ${CMAKE_CURRENT_BINARY_DIR}/hint_testing.output -c ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/hint_testing.expected") +add_test(TestFwfilter /bin/sh -c "MAXSCALE_HOME=\"${CMAKE_BINARY_DIR}\" ${CMAKE_CURRENT_BINARY_DIR}/harness -i ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.input -o ${CMAKE_CURRENT_BINARY_DIR}/fwtest.output -c ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.cnf -t 1 -s 1 -e ${CMAKE_CURRENT_SOURCE_DIR}/fwtest.expected") \ No newline at end of file diff --git a/server/modules/filter/test/fwtest.cnf b/server/modules/filter/test/fwtest.cnf new file mode 100644 index 000000000..ed27b67cd --- /dev/null +++ b/server/modules/filter/test/fwtest.cnf @@ -0,0 +1,159 @@ +# +# Example MaxScale.cnf configuration file +# +# +# +# Number of server threads +# Valid options are: +# threads= + +[maxscale] +threads=1 + +# Define a monitor that can be used to determine the state and role of +# the servers. +# +# Valid options are: +# +# module= +# servers=,,... +# user = +# passwd= +# monitor_interval= + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3,server4 +user=maxuser +passwd=maxpwd + +# A series of service definition +# +# Valid options are: +# +# router= +# servers=,,... +# user= +# passwd= +# enable_root_user=<0 or 1, default is 0> +# version_string= +# +# Valid router modules currently are: +# readwritesplit, readconnroute and debugcli + + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +#filters=MQ + +[RW Split Hint Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +filters=Hint + + +[Read Connection Router] +type=service +router=readconnroute +router_options=master +servers=server1 +user=maxuser +passwd=maxpwd + + +[HTTPD Router] +type=service +router=testroute +servers=server1,server2,server3 + +[Debug Interface] +type=service +router=debugcli + + +[Hint] +type=filter +module=fwfilter +rules=@CMAKE_CURRENT_BINARY_DIR@/rules + + +# Listener definitions for the services +# +# Valid options are: +# +# service= +# protocol= +# port= +# address=
+# socket= + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[RW Split Hint Listener] +type=listener +service=RW Split Hint Router +protocol=MySQLClient +port=4009 + +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 +#socket=/tmp/readconn.sock + +[Debug Listener] +type=listener +service=Debug Interface +protocol=telnetd +port=4442 +#address=127.0.0.1 + +[HTTPD Listener] +type=listener +service=HTTPD Router +protocol=HTTPD +port=6444 + +# Definition of the servers + +[server1] +type=server +address=127.0.0.1 +port=3000 +protocol=MySQLBackend + +[server2] +type=server +address=127.0.0.1 +port=3001 +protocol=MySQLBackend + +[server3] +type=server +address=127.0.0.1 +port=3002 +protocol=MySQLBackend + +[server4] +type=server +address=127.0.0.1 +port=3003 +protocol=MySQLBackend diff --git a/server/modules/filter/test/fwtest.cnf.in b/server/modules/filter/test/fwtest.cnf.in new file mode 100644 index 000000000..ef1623c8b --- /dev/null +++ b/server/modules/filter/test/fwtest.cnf.in @@ -0,0 +1,159 @@ +# +# Example MaxScale.cnf configuration file +# +# +# +# Number of server threads +# Valid options are: +# threads= + +[maxscale] +threads=1 + +# Define a monitor that can be used to determine the state and role of +# the servers. +# +# Valid options are: +# +# module= +# servers=,,... +# user = +# passwd= +# monitor_interval= + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3,server4 +user=maxuser +passwd=maxpwd + +# A series of service definition +# +# Valid options are: +# +# router= +# servers=,,... +# user= +# passwd= +# enable_root_user=<0 or 1, default is 0> +# version_string= +# +# Valid router modules currently are: +# readwritesplit, readconnroute and debugcli + + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +#filters=MQ + +[RW Split Hint Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=90% +user=maxuser +passwd=maxpwd +filters=Hint + + +[Read Connection Router] +type=service +router=readconnroute +router_options=master +servers=server1 +user=maxuser +passwd=maxpwd + + +[HTTPD Router] +type=service +router=testroute +servers=server1,server2,server3 + +[Debug Interface] +type=service +router=debugcli + + +[Hint] +type=filter +module=fwfilter +rules=@CMAKE_CURRENT_SOURCE_DIR@/rules + + +# Listener definitions for the services +# +# Valid options are: +# +# service= +# protocol= +# port= +# address=
+# socket= + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[RW Split Hint Listener] +type=listener +service=RW Split Hint Router +protocol=MySQLClient +port=4009 + +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 +#socket=/tmp/readconn.sock + +[Debug Listener] +type=listener +service=Debug Interface +protocol=telnetd +port=4442 +#address=127.0.0.1 + +[HTTPD Listener] +type=listener +service=HTTPD Router +protocol=HTTPD +port=6444 + +# Definition of the servers + +[server1] +type=server +address=127.0.0.1 +port=3000 +protocol=MySQLBackend + +[server2] +type=server +address=127.0.0.1 +port=3001 +protocol=MySQLBackend + +[server3] +type=server +address=127.0.0.1 +port=3002 +protocol=MySQLBackend + +[server4] +type=server +address=127.0.0.1 +port=3003 +protocol=MySQLBackend diff --git a/server/modules/filter/test/fwtest.input b/server/modules/filter/test/fwtest.input new file mode 100644 index 000000000..befe720c6 --- /dev/null +++ b/server/modules/filter/test/fwtest.input @@ -0,0 +1,7 @@ +select 1; +delete from t1; +select 1; +update t1 set id=1; +select 1; +select id from t1 union select User from mysql.users; +select 1; \ No newline at end of file diff --git a/server/modules/filter/test/harness.h b/server/modules/filter/test/harness.h index c30c266c2..a306a6811 100644 --- a/server/modules/filter/test/harness.h +++ b/server/modules/filter/test/harness.h @@ -69,6 +69,7 @@ #include #include #include +#include /** * A single name-value pair and a link to the next item in the * configuration. @@ -117,6 +118,7 @@ typedef struct int running; int verbose; /**Whether to print to stdout*/ int infile; /**A file where the queries are loaded from*/ + int expected; int error; char* mod_dir; /**Module directory absolute path*/ char* infile_name; @@ -172,7 +174,7 @@ typedef packet_t PACKET; /** * Initialize the static instance. */ -int harness_init(int argc,char** argv); +int harness_init(int argc,char** argv,HARNESS_INSTANCE** inst); /** * Frees all the query buffers @@ -359,4 +361,14 @@ GWBUF* gen_packet(PACKET pkt); */ int process_opts(int argc, char** argv); +/** + * Compares the contents of two files. + * This function resets the offsets of the file descriptors and leaves them in an + * undefined state. + * @param a The first file + * @param b The second file + * @return 0 if the files do not differ and 1 if they do or an error occurred. + */ +int compare_files(int a, int b); + #endif diff --git a/server/modules/filter/test/harness_common.c b/server/modules/filter/test/harness_common.c index fe782bc0d..c775333aa 100644 --- a/server/modules/filter/test/harness_common.c +++ b/server/modules/filter/test/harness_common.c @@ -1,6 +1,6 @@ #include -int harness_init(int argc, char** argv){ +int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){ int i = 0; if(!(argc == 2 && strcmp(argv[1],"-h") == 0)){ skygw_logmanager_init(0,NULL); @@ -14,9 +14,11 @@ int harness_init(int argc, char** argv){ return 1; } + *inst = &instance; instance.running = 1; instance.infile = -1; instance.outfile = -1; + instance.expected = -1; instance.buff_ind = -1; instance.last_ind = -1; instance.sess_ind = -1; @@ -84,15 +86,17 @@ void free_buffers() } int open_file(char* str, unsigned int write) { - int mode; + int mode,fd; if(write){ mode = O_RDWR|O_CREAT; }else{ mode = O_RDONLY; } - - return open(str,mode,S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH); + if((fd = open(str,mode,S_IRWXU|S_IRGRP|S_IXGRP|S_IXOTH)) < 0){ + printf("Error %d: %s\n",errno,strerror(errno)); + } + return fd; } @@ -608,6 +612,8 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) { FILTER_PARAMETER** fparams = NULL; int i, paramc = -1; + int sess_err = 0; + int x; if(cnf == NULL){ fparams = read_params(¶mc); @@ -672,13 +678,16 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) } } - int sess_err = 0; if(cnf && fc && fc->instance){ fc->filter = (FILTER*)fc->instance->createInstance(NULL,fparams); - + if(fc->filter == NULL){ + printf("Error loading filter:%s: createInstance returned NULL.\n",fc->name); + sess_err = 1; + goto error; + } for(i = 0;isession[i] = fc->instance->newSession(fc->filter, fc->session[i])) && @@ -746,8 +755,8 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) } } - - int x; + error: + if(fparams){ for(x = 0;xinstance->routeQuery(instance.head->filter, + if(instance.head->instance->routeQuery(instance.head->filter, instance.head->session[index], - instance.buffer[instance.buff_ind]); + instance.buffer[instance.buff_ind]) == 0){ + if(instance.outfile > 0){ + const char* msg = "Query returned 0.\n"; + write(instance.outfile,msg,strlen(msg)); + } + } if(instance.tail->instance->clientReply){ instance.tail->instance->clientReply(instance.tail->filter, instance.tail->session[index], @@ -929,10 +943,11 @@ GWBUF* gen_packet(PACKET pkt) } + int process_opts(int argc, char** argv) { int fd, buffsize = 1024; - int rd,rdsz, rval; + int rd,rdsz, rval = 0; size_t fsize; char *buff = calloc(buffsize,sizeof(char)), *tok = NULL; @@ -985,10 +1000,18 @@ int process_opts(int argc, char** argv) close(fd); return 1; } + char* conf_name = NULL; - while((rd = getopt(argc,argv,"m:c:i:o:s:t:d:qh")) > 0){ + rval = 0; + + while((rd = getopt(argc,argv,"e:m:c:i:o:s:t:d:qh")) > 0){ switch(rd){ + case 'e': + instance.expected = open_file(optarg,0); + printf("Expected output is read from: %s\n",optarg); + break; + case 'o': instance.outfile = open_file(optarg,1); printf("Output is written to: %s\n",optarg); @@ -1053,6 +1076,7 @@ int process_opts(int argc, char** argv) } } printf("\n"); + if(conf_name && load_config(conf_name)){ load_query(); }else{ @@ -1061,5 +1085,30 @@ int process_opts(int argc, char** argv) free(conf_name); close(fd); + return rval; +} + +int compare_files(int a,int b) +{ + char in[4098]; + char exp[4098]; + int line = 1; + + if(a < 1 || b < 1){ + return 1; + } + + if(lseek(a,0,SEEK_SET) < 0 || + lseek(b,0,SEEK_SET) < 0){ + return 1; + } + + while(fdgets(a,in,4098) && fdgets(b,exp,4098)){ + if(strcmp(in,exp)){ + printf("The files differ at line %d:\n%s\n-------------------------------------\n%s\n",line,in,exp); + return 1; + } + line++; + } return 0; } diff --git a/server/modules/filter/test/harness_ui.c b/server/modules/filter/test/harness_ui.c index 9b38f648c..8921a88bc 100755 --- a/server/modules/filter/test/harness_ui.c +++ b/server/modules/filter/test/harness_ui.c @@ -6,9 +6,9 @@ int main(int argc, char** argv){ char* tk; FILTERCHAIN* tmp_chn; FILTERCHAIN* del_chn; - + HARNESS_INSTANCE* hinstance; - if(harness_init(argc,argv)){ + if(harness_init(argc,argv,&hinstance)){ printf("Error: Initialization failed.\n"); skygw_log_write(LOGFILE_ERROR,"Error: Initialization failed.\n"); skygw_logmanager_done(); diff --git a/server/modules/filter/test/harness_util.c b/server/modules/filter/test/harness_util.c index fb6905445..705bc3c3b 100644 --- a/server/modules/filter/test/harness_util.c +++ b/server/modules/filter/test/harness_util.c @@ -1,7 +1,8 @@ #include int main(int argc,char** argv) { - if(harness_init(argc,argv) || instance.error){ + HARNESS_INSTANCE* inst; + if(harness_init(argc,argv,&inst) || inst->error){ printf("Error: Initialization failed.\n"); skygw_log_write(LOGFILE_ERROR,"Error: Initialization failed.\n"); skygw_logmanager_done(); @@ -10,5 +11,8 @@ int main(int argc,char** argv) } route_buffers(); + if(inst->expected){ + return compare_files(inst->outfile,inst->expected); + } return 0; } diff --git a/server/modules/filter/test/rules b/server/modules/filter/test/rules new file mode 100644 index 000000000..8e6c4ab3c --- /dev/null +++ b/server/modules/filter/test/rules @@ -0,0 +1,3 @@ +rule union_regex deny regex '.*union.*' +rule dont_delete_everything deny no_where_clause on_operations delete|update at_times 12:00:00-18:00:00 +users %@% match any rules union_regex dont_delete_everything \ No newline at end of file