Updated test harness and added preliminary firewall filter tests

This commit is contained in:
Markus Makela
2014-11-17 14:52:26 +02:00
parent 1399a33944
commit f233bfab66
10 changed files with 442 additions and 47 deletions

View File

@ -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=<path to file>
* rules=<path to file> 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 <my_config.h>
#include <stdint.h>
@ -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

View File

@ -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 $?")
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")

View File

@ -0,0 +1,159 @@
#
# Example MaxScale.cnf configuration file
#
#
#
# Number of server threads
# Valid options are:
# threads=<number of threads>
[maxscale]
threads=1
# Define a monitor that can be used to determine the state and role of
# the servers.
#
# Valid options are:
#
# module=<name of module to load>
# servers=<server name>,<server name>,...
# user =<user name - must have slave replication and
# slave client privileges>
# passwd=<password of the above user, plain text currently>
# monitor_interval=<sampling interval in milliseconds,
# default value is 10000>
[MySQL Monitor]
type=monitor
module=mysqlmon
servers=server1,server2,server3,server4
user=maxuser
passwd=maxpwd
# A series of service definition
#
# Valid options are:
#
# router=<name of router module>
# servers=<server name>,<server name>,...
# user=<User to fetch password inforamtion with>
# passwd=<Password of the user, plain text currently>
# enable_root_user=<0 or 1, default is 0>
# version_string=<specific string for server handshake,
# default is the MariaDB embedded library version>
#
# 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=<name of service defined elsewhere>
# protocol=<name of protocol module with which to listen>
# port=<Listening port>
# address=<Address to bind to>
# socket=<Listening 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

View File

@ -0,0 +1,159 @@
#
# Example MaxScale.cnf configuration file
#
#
#
# Number of server threads
# Valid options are:
# threads=<number of threads>
[maxscale]
threads=1
# Define a monitor that can be used to determine the state and role of
# the servers.
#
# Valid options are:
#
# module=<name of module to load>
# servers=<server name>,<server name>,...
# user =<user name - must have slave replication and
# slave client privileges>
# passwd=<password of the above user, plain text currently>
# monitor_interval=<sampling interval in milliseconds,
# default value is 10000>
[MySQL Monitor]
type=monitor
module=mysqlmon
servers=server1,server2,server3,server4
user=maxuser
passwd=maxpwd
# A series of service definition
#
# Valid options are:
#
# router=<name of router module>
# servers=<server name>,<server name>,...
# user=<User to fetch password inforamtion with>
# passwd=<Password of the user, plain text currently>
# enable_root_user=<0 or 1, default is 0>
# version_string=<specific string for server handshake,
# default is the MariaDB embedded library version>
#
# 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=<name of service defined elsewhere>
# protocol=<name of protocol module with which to listen>
# port=<Listening port>
# address=<Address to bind to>
# socket=<Listening 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

View File

@ -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;

View File

@ -69,6 +69,7 @@
#include <ini.h>
#include <hint.h>
#include <modutil.h>
#include <errno.h>
/**
* 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

View File

@ -1,6 +1,6 @@
#include <harness.h>
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(&paramc);
@ -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;i<instance.session_count;i++){
if((fc->session[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;x<paramc;x++){
@ -863,9 +872,14 @@ void work_buffer(void* thr_num)
index < instance.session_count &&
instance.buff_ind < instance.buffer_count)
{
instance.head->instance->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;
}

View File

@ -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();

View File

@ -1,7 +1,8 @@
#include <harness.h>
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;
}

View File

@ -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