From 624872ef4889cb43f5ecb42b74d9722bd6e9a4a8 Mon Sep 17 00:00:00 2001 From: Grant Pannell Date: Sat, 29 Nov 2014 15:05:06 +1030 Subject: [PATCH] Add DESTINATION access.conf directive and ENABLE_DESTINATION_RULE fwknopd.conf directive --- Makefile.am | 1 + doc/fwknopd.man.asciidoc | 18 ++++++++ server/access.c | 51 ++++++++++++++++------ server/access.conf | 13 ++++++ server/cmd_opts.h | 1 + server/config_init.c | 6 +++ server/fw_util_firewalld.c | 58 ++++++++++++++++--------- server/fw_util_firewalld.h | 7 +-- server/fw_util_ipf.c | 6 ++- server/fw_util_ipf.h | 1 + server/fw_util_ipfw.c | 6 +++ server/fw_util_ipfw.h | 3 +- server/fw_util_iptables.c | 58 ++++++++++++++++--------- server/fw_util_iptables.h | 7 +-- server/fw_util_pf.c | 6 +++ server/fw_util_pf.h | 5 ++- server/fwknopd.8.in | 12 +++++ server/fwknopd.conf | 10 +++++ server/fwknopd_common.h | 13 ++++++ server/incoming_spa.c | 8 +++- test/conf/destination_rule_fwknopd.conf | 1 + test/tests/basic_operations.pl | 16 +++++++ 22 files changed, 242 insertions(+), 65 deletions(-) create mode 100644 test/conf/destination_rule_fwknopd.conf diff --git a/Makefile.am b/Makefile.am index 251b17c2..d9cda93e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -314,6 +314,7 @@ EXTRA_DIST = \ test/conf/firewd_snat_fwknopd.conf \ test/conf/ipt_snat_no_translate_ip_fwknopd.conf \ test/conf/firewd_snat_no_translate_ip_fwknopd.conf \ + test/conf/destination_rule_fwknopd.conf \ test/conf/spa_replay.pcap \ test/conf/fcs_spa.pcap \ test/fko-wrapper/Makefile \ diff --git a/doc/fwknopd.man.asciidoc b/doc/fwknopd.man.asciidoc index 6a503b50..e0af2a2d 100644 --- a/doc/fwknopd.man.asciidoc +++ b/doc/fwknopd.man.asciidoc @@ -384,6 +384,15 @@ See the '@sysconfdir@/fwknop/fwknopd.conf' file for the full list and correspond *SYSLOG_FACILITY* '':: Override syslog facility. The ``SYSLOG_FACILITY'' variable can be set to one of ``LOG_LOCAL{0-7}'' or ``LOG_DAEMON'' (the default). + +*ENABLE_DESTINATION_RULE* '':: + Controls whether *fwknopd* will set the destination field on the firewall + rule to the destination address specified on the incoming SPA packet. + This is useful for interfaces with multiple IP addresses hosting separate + services. If ``ENABLE_IPT_OUTPUT'' is set to ``Y'', the source field of + the firewall rule is set. FORWARD and SNAT rules are not affected however, + DNAT rules will also have their destination field set. The default is + ``N'', which sets the destination field to 0.0.0.0/0 (any). *FWKNOP_RUN_DIR* '':: Specify the directory where *fwknopd* writes run time state files. The @@ -411,6 +420,15 @@ directive starts a new stanza. and individual IP addresses can be specified as well. Also, multiple IP's and/or networks can be defined as a comma separated list (e.g. ``192.168.10.0/24,10.1.1.123'') + +*DESTINATION* '':: + This defines the destination address which the SPA packet will be + accepted. The string ``ANY'' is also accepted if a valid SPA packet + should be honored to any destination IP. + Networks should be specified in CIDR notation (e.g. ``192.168.10.0/24''), + and individual IP addresses can be specified as well. Also, multiple + IP's and/or networks can be defined as a comma separated list (e.g. + ``192.168.10.0/24,10.1.1.123'') *OPEN_PORTS* ',...,':: Define a set of ports and protocols (tcp or udp) that will be diff --git a/server/access.c b/server/access.c index eb8c9df2..a7ae679a 100644 --- a/server/access.c +++ b/server/access.c @@ -229,7 +229,7 @@ add_acc_force_snat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char * * comparisons of incoming source IPs against this mask. */ static int -add_source_mask(fko_srv_options_t *opts, acc_stanza_t *acc, const char *ip) +add_int_ent(acc_int_list_t **ilist, const char *ip) { char *ndx; char ip_str[MAX_IPV4_STR_LEN] = {0}; @@ -247,7 +247,7 @@ add_source_mask(fko_srv_options_t *opts, acc_stanza_t *acc, const char *ip) log_msg(LOG_ERR, "[*] Fatal memory allocation error adding stanza source_list entry" ); - clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + exit(EXIT_FAILURE); } /* Convert the IP data into the appropriate IP + (optional) mask @@ -370,13 +370,13 @@ add_source_mask(fko_srv_options_t *opts, acc_stanza_t *acc, const char *ip) /* If this is not the first entry, we walk our pointer to the * end of the list. */ - if(acc->source_list == NULL) + if(*ilist == NULL) { - acc->source_list = new_sle; + *ilist = new_sle; } else { - tmp_sle = acc->source_list; + tmp_sle = *ilist; do { last_sle = tmp_sle; @@ -391,13 +391,13 @@ add_source_mask(fko_srv_options_t *opts, acc_stanza_t *acc, const char *ip) /* Expand the access SOURCE string to a list of masks. */ static int -expand_acc_source(fko_srv_options_t *opts, acc_stanza_t *acc) +expand_acc_int_list(acc_int_list_t **ilist, char *ip) { char *ndx, *start; char buf[ACCESS_BUF_LEN] = {0}; int res = 1; - start = acc->source; + start = ip; for(ndx = start; *ndx; ndx++) { @@ -413,7 +413,7 @@ expand_acc_source(fko_srv_options_t *opts, acc_stanza_t *acc) strlcpy(buf, start, (ndx-start)+1); - res = add_source_mask(opts, acc, buf); + res = add_int_ent(ilist, buf); if(res == 0) return res; @@ -431,7 +431,7 @@ expand_acc_source(fko_srv_options_t *opts, acc_stanza_t *acc) strlcpy(buf, start, (ndx-start)+1); - res = add_source_mask(opts, acc, buf); + res = add_int_ent(ilist, buf); return res; } @@ -504,7 +504,7 @@ add_port_list_ent(acc_port_list_t **plist, char *port_str) if((new_plist = calloc(1, sizeof(acc_port_list_t))) == NULL) { log_msg(LOG_ERR, - "[*] Fatal memory allocation error adding stanza source_list entry" + "[*] Fatal memory allocation error adding stanza port_list entry" ); exit(EXIT_FAILURE); } @@ -677,7 +677,7 @@ expand_acc_string_list(acc_string_list_t **stlist, char *stlist_str) /* Free the acc source_list */ static void -free_acc_source_list(acc_int_list_t *sle) +free_acc_int_list(acc_int_list_t *sle) { acc_int_list_t *last_sle; @@ -747,7 +747,13 @@ free_acc_stanza_data(acc_stanza_t *acc) if(acc->source != NULL) { free(acc->source); - free_acc_source_list(acc->source_list); + free_acc_int_list(acc->source_list); + } + + if(acc->destination != NULL) + { + free(acc->destination); + free_acc_int_list(acc->destination_list); } if(acc->open_ports != NULL) @@ -839,11 +845,20 @@ expand_acc_ent_lists(fko_srv_options_t *opts) { /* Expand the source string to 32-bit integer IP + masks for each entry. */ - if(expand_acc_source(opts, acc) == 0) + if(expand_acc_int_list(&(acc->source_list), acc->source) == 0) { log_msg(LOG_ERR, "[*] Fatal invalid SOURCE in access stanza"); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); } + + if(acc->destination != NULL && strlen(acc->destination)) + { + if(expand_acc_int_list(&(acc->destination_list), acc->destination) == 0) + { + log_msg(LOG_ERR, "[*] Fatal invalid DESTINATION in access stanza"); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + } /* Now expand the open_ports string. */ @@ -1290,6 +1305,14 @@ parse_access_file(fko_srv_options_t *opts) */ continue; } + else if(CONF_VAR_IS(var, "DESTINATION")) + { + if(add_acc_string(&(curr_acc->destination), val) != SUCCESS) + { + fclose(file_ptr); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + } else if(CONF_VAR_IS(var, "OPEN_PORTS")) { if(add_acc_string(&(curr_acc->open_ports), val) != SUCCESS) @@ -1898,6 +1921,7 @@ dump_access_list(const fko_srv_options_t *opts) fprintf(stdout, "SOURCE (%i): %s\n" "==============================================================\n" + " DESTINATION: %s\n" " OPEN_PORTS: %s\n" " RESTRICT_PORTS: %s\n" " KEY: %s\n" @@ -1928,6 +1952,7 @@ dump_access_list(const fko_srv_options_t *opts) " GPG_FINGERPRINT_ID: %s\n", ++i, acc->source, + (acc->destination == NULL) ? "" : acc->destination, (acc->open_ports == NULL) ? "" : acc->open_ports, (acc->restrict_ports == NULL) ? "" : acc->restrict_ports, (acc->key == NULL) ? "" : "", diff --git a/server/access.conf b/server/access.conf index f61ad872..22e90c96 100644 --- a/server/access.conf +++ b/server/access.conf @@ -36,6 +36,19 @@ # be honored from any source IP. # +# DESTINATION +# +# This defines the destination address from which a SPA packet will be accepted. +# Networks should be specified in CIDR (e.g. "192.168.10.0/24") notation. +# Individual IP addresses can be specified as well. +# +# Also, multiple IP’s and/or networks can be defined as a comma-separated +# list (e.g. "192.168.10.0/24,10.1.1.123"). +# +# The string "ANY" is also accepted if a valid authorization packet should +# be honored to any destination IP. +# + # OPEN_PORTS , ..., config[CONF_ENABLE_DIGEST_PERSISTENCE] == NULL) set_config_entry(opts, CONF_ENABLE_DIGEST_PERSISTENCE, DEF_ENABLE_DIGEST_PERSISTENCE); + + /* Enable destination rule. + */ + if(opts->config[CONF_ENABLE_DESTINATION_RULE] == NULL) + set_config_entry(opts, CONF_ENABLE_DESTINATION_RULE, + DEF_ENABLE_DESTINATION_RULE); /* Max sniff bytes. */ diff --git a/server/fw_util_firewalld.c b/server/fw_util_firewalld.c index 908a4dd0..5fb054b9 100644 --- a/server/fw_util_firewalld.c +++ b/server/fw_util_firewalld.c @@ -70,14 +70,15 @@ chop_newline(char *str) static int rule_exists_no_chk_support(const fko_srv_options_t * const opts, const struct fw_chain * const fwc, const unsigned int proto, - const char * const ip, const unsigned int port, - const unsigned int exp_ts) + const char * const srcip, const char * const dstip, + const unsigned int port, const unsigned int exp_ts) { int rule_exists=0, rule_num=0, rtmp=0; char cmd_buf[CMD_BUFSIZE] = {0}; char target_search[CMD_BUFSIZE] = {0}; char proto_search[CMD_BUFSIZE] = {0}; - char ip_search[CMD_BUFSIZE] = {0}; + char srcip_search[CMD_BUFSIZE] = {0}; + char dstip_search[CMD_BUFSIZE] = {0}; char port_search[CMD_BUFSIZE] = {0}; char exp_ts_search[CMD_BUFSIZE] = {0}; @@ -98,7 +99,11 @@ rule_exists_no_chk_support(const fko_srv_options_t * const opts, snprintf(port_search, CMD_BUFSIZE-1, ":%u ", port); snprintf(target_search, CMD_BUFSIZE-1, " %s ", fwc->target); - snprintf(ip_search, CMD_BUFSIZE-1, " %s ", ip); + snprintf(srcip_search, CMD_BUFSIZE-1, " %s ", srcip); + if (dstip != NULL) + { + snprintf(dstip_search, CMD_BUFSIZE-1, " %s ", dstip); + } snprintf(exp_ts_search, CMD_BUFSIZE-1, "%u ", exp_ts); /* search for each of the substrings, and require the returned @@ -114,25 +119,28 @@ rule_exists_no_chk_support(const fko_srv_options_t * const opts, NO_TIMEOUT, proto_search, &pid_status, opts); if(rtmp == rule_num) rtmp = search_extcmd(cmd_buf, WANT_STDERR, - NO_TIMEOUT, ip_search, &pid_status, opts); + NO_TIMEOUT, srcip_search, &pid_status, opts); if(rtmp == rule_num) - rtmp = search_extcmd(cmd_buf, WANT_STDERR, - NO_TIMEOUT, target_search, &pid_status, opts); + rtmp = (dstip == NULL) ? rtmp : search_extcmd(cmd_buf, WANT_STDERR, + NO_TIMEOUT, dstip_search, &pid_status, opts); if(rtmp == rule_num) rtmp = search_extcmd(cmd_buf, WANT_STDERR, - NO_TIMEOUT, port_search, &pid_status, opts); + NO_TIMEOUT, target_search, &pid_status, opts); if(rtmp == rule_num) - rule_exists = 1; + rtmp = search_extcmd(cmd_buf, WANT_STDERR, + NO_TIMEOUT, port_search, &pid_status, opts); + if(rtmp == rule_num) + rule_exists = 1; } if(rule_exists) log_msg(LOG_DEBUG, "rule_exists_no_chk_support() %s %u -> %s expires: %u rule (already exists", - proto_search, port, ip, exp_ts); + proto_search, port, srcip, exp_ts); else log_msg(LOG_DEBUG, "rule_exists_no_chk_support() %s %u -> %s expires: %u rule does not exist", - proto_search, port, ip, exp_ts); + proto_search, port, srcip, exp_ts); return(rule_exists); } @@ -174,15 +182,16 @@ rule_exists_chk_support(const fko_srv_options_t * const opts, static int rule_exists(const fko_srv_options_t * const opts, const struct fw_chain * const fwc, const char * const rule, - const unsigned int proto, const char * const ip, - const unsigned int port, const unsigned int exp_ts) + const unsigned int proto, const char * const srcip, + const char * const dstip, const unsigned int port, + const unsigned int exp_ts) { int rule_exists = 0; if(have_firewd_chk_support == 1) rule_exists = rule_exists_chk_support(opts, fwc->to_chain, rule); else - rule_exists = rule_exists_no_chk_support(opts, fwc, proto, ip, port, exp_ts); + rule_exists = rule_exists_no_chk_support(opts, fwc, proto, srcip, (opts->fw_config->use_destination ? dstip : NULL), port, exp_ts); if(rule_exists == 1) log_msg(LOG_DEBUG, "rule_exists() Rule : '%s' in %s already exists", @@ -857,6 +866,11 @@ fw_config_init(fko_srv_options_t * const opts) } } } + + if(strncasecmp(opts->config[CONF_ENABLE_DESTINATION_RULE], "Y", 1)==0) + { + fwc.use_destination = 1; + } /* Let us find it via our opts struct as well. */ @@ -1042,13 +1056,14 @@ process_spa_request(const fko_srv_options_t * const opts, in_chain->table, ple->proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP) ple->port, exp_ts, in_chain->target ); if(rule_exists(opts, in_chain, rule_buf, - ple->proto, spadat->use_src_ip, ple->port, exp_ts) == 0) + ple->proto, spadat->use_src_ip, spadat->pkt_destination_ip, ple->port, exp_ts) == 0) { if(create_rule(opts, in_chain->to_chain, rule_buf)) { @@ -1078,13 +1093,14 @@ process_spa_request(const fko_srv_options_t * const opts, out_chain->table, ple->proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), ple->port, exp_ts, out_chain->target ); if(rule_exists(opts, out_chain, rule_buf, - ple->proto, spadat->use_src_ip, ple->port, exp_ts) == 0) + ple->proto, spadat->use_src_ip, spadat->pkt_destination_ip, ple->port, exp_ts) == 0) { if(create_rule(opts, out_chain->to_chain, rule_buf)) { @@ -1151,6 +1167,7 @@ process_spa_request(const fko_srv_options_t * const opts, in_chain->table, fst_proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), nat_port, exp_ts, in_chain->target @@ -1166,7 +1183,7 @@ process_spa_request(const fko_srv_options_t * const opts, add_jump_rule(opts, FIREWD_INPUT_ACCESS); if(rule_exists(opts, in_chain, rule_buf, - fst_proto, spadat->use_src_ip, nat_port, exp_ts) == 0) + fst_proto, spadat->use_src_ip, spadat->pkt_destination_ip, nat_port, exp_ts) == 0) { if(create_rule(opts, in_chain->to_chain, rule_buf)) { @@ -1209,7 +1226,7 @@ process_spa_request(const fko_srv_options_t * const opts, ); if(rule_exists(opts, fwd_chain, rule_buf, fst_proto, - spadat->use_src_ip, nat_port, exp_ts) == 0) + spadat->use_src_ip, nat_ip, nat_port, exp_ts) == 0) { if(create_rule(opts, fwd_chain->to_chain, rule_buf)) { @@ -1245,6 +1262,7 @@ process_spa_request(const fko_srv_options_t * const opts, dnat_chain->table, fst_proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : FIREWD_ANY_IP), fst_port, exp_ts, dnat_chain->target, @@ -1253,7 +1271,7 @@ process_spa_request(const fko_srv_options_t * const opts, ); if(rule_exists(opts, dnat_chain, rule_buf, fst_proto, - spadat->use_src_ip, fst_port, exp_ts) == 0) + spadat->use_src_ip, spadat->pkt_destination_ip, fst_port, exp_ts) == 0) { if(create_rule(opts, dnat_chain->to_chain, rule_buf)) { @@ -1333,7 +1351,7 @@ process_spa_request(const fko_srv_options_t * const opts, ); if(rule_exists(opts, snat_chain, rule_buf, fst_proto, - spadat->use_src_ip, nat_port, exp_ts) == 0) + spadat->use_src_ip, NULL, nat_port, exp_ts) == 0) { if(create_rule(opts, snat_chain->to_chain, rule_buf)) { diff --git a/server/fw_util_firewalld.h b/server/fw_util_firewalld.h index 8b17c4c9..b91131d2 100644 --- a/server/fw_util_firewalld.h +++ b/server/fw_util_firewalld.h @@ -42,10 +42,10 @@ /* firewalld command args */ #define FIREWD_CHK_RULE_ARGS "-C %s %s" /* the other macros add SH_REDIR if necessary */ -#define FIREWD_RULE_ARGS "-t %s -p %i -s %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR -#define FIREWD_OUT_RULE_ARGS "-t %s -p %i -d %s --sport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR +#define FIREWD_RULE_ARGS "-t %s -p %i -s %s -d %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR +#define FIREWD_OUT_RULE_ARGS "-t %s -p %i -d %s -s %s --sport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR #define FIREWD_FWD_RULE_ARGS "-t %s -p %i -s %s -d %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR -#define FIREWD_DNAT_RULE_ARGS "-t %s -p %i -s %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s --to-destination %s:%i" SH_REDIR +#define FIREWD_DNAT_RULE_ARGS "-t %s -p %i -s %s -d %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s --to-destination %s:%i" SH_REDIR #define FIREWD_SNAT_RULE_ARGS "-t %s -p %i -d %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s %s" SH_REDIR #define FIREWD_TMP_COMMENT_ARGS "-t %s -I %s %i -s 127.0.0.2 -m comment --comment " TMP_COMMENT " -j %s" SH_REDIR #define FIREWD_TMP_CHK_RULE_ARGS "-t %s -I %s %i -s 127.0.0.2 -p udp -j %s" SH_REDIR @@ -60,6 +60,7 @@ #define FIREWD_DEL_JUMP_RULE_ARGS "-t %s -D %s -j %s" SH_REDIR /* let firewalld work out the rule number */ #define FIREWD_LIST_RULES_ARGS "-t %s -L %s --line-numbers -n" SH_REDIR #define FIREWD_LIST_ALL_RULES_ARGS "-t %s -v -n -L --line-numbers" SH_REDIR +#define FIREWD_ANY_IP "0.0.0.0/0" int validate_firewd_chain_conf(const char * const chain_str); diff --git a/server/fw_util_ipf.c b/server/fw_util_ipf.c index 1e875b91..dbb4f499 100644 --- a/server/fw_util_ipf.c +++ b/server/fw_util_ipf.c @@ -72,7 +72,11 @@ fw_config_init(fko_srv_options_t *opts) /* Set our firewall exe command path (iptables in most cases). */ strlcpy(fwc.fw_command, opts->config[CONF_FIREWALL_EXE], sizeof(fwc.fw_command)); - + + if(strncasecmp(opts->config[CONF_ENABLE_DESTINATION_RULE], "Y", 1)==0) + { + fwc.use_destination = 1; + } /* Let us find it via our opts struct as well. */ diff --git a/server/fw_util_ipf.h b/server/fw_util_ipf.h index 966607d5..cd023b28 100644 --- a/server/fw_util_ipf.h +++ b/server/fw_util_ipf.h @@ -40,6 +40,7 @@ #define IPF_ADD_SNAT_RULE_ARGS "" #define IPF_DEL_RULE_ARGS "" #define IPF_LIST_RULES_ARGS "" +#define IPF_ANY_IP "" #endif /* FW_UTIL_IPF_H */ diff --git a/server/fw_util_ipfw.c b/server/fw_util_ipfw.c index 932164f9..1025b4e6 100644 --- a/server/fw_util_ipfw.c +++ b/server/fw_util_ipfw.c @@ -238,6 +238,11 @@ fw_config_init(fko_srv_options_t * const opts) RCHK_MAX_IPFW_PURGE_INTERVAL); return 0; } + + if(strncasecmp(opts->config[CONF_ENABLE_DESTINATION_RULE], "Y", 1)==0) + { + fwc.use_destination = 1; + } /* Let us find it via our opts struct as well. */ @@ -532,6 +537,7 @@ process_spa_request(const fko_srv_options_t * const opts, fwc.active_set_num, ple->proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : IPFW_ANY_IP), ple->port, exp_ts ); diff --git a/server/fw_util_ipfw.h b/server/fw_util_ipfw.h index fa716761..031221fa 100644 --- a/server/fw_util_ipfw.h +++ b/server/fw_util_ipfw.h @@ -40,13 +40,14 @@ enum { /* ipfw command args */ -#define IPFW_ADD_RULE_ARGS "add %u set %u pass %u from %s to me dst-port %u setup keep-state // " EXPIRE_COMMENT_PREFIX "%u" +#define IPFW_ADD_RULE_ARGS "add %u set %u pass %u from %s to %s dst-port %u setup keep-state // " EXPIRE_COMMENT_PREFIX "%u" #define IPFW_ADD_CHECK_STATE_ARGS "add %u set %u check-state" #define IPFW_MOVE_RULE_ARGS "set move rule %u to %u" #define IPFW_MOVE_SET_ARGS "set move %u to %u" #define IPFW_DISABLE_SET_ARGS "set disable %u" #define IPFW_LIST_ALL_RULES_ARGS "list" #define IPFW_DEL_RULE_SET_ARGS "delete set %u" +#define IPFW_ANY_IP "me" #ifdef __APPLE__ #define IPFW_DEL_RULE_ARGS "delete %u" //--DSS diff args diff --git a/server/fw_util_iptables.c b/server/fw_util_iptables.c index 8492fa83..55fb6e84 100644 --- a/server/fw_util_iptables.c +++ b/server/fw_util_iptables.c @@ -70,14 +70,15 @@ chop_newline(char *str) static int rule_exists_no_chk_support(const fko_srv_options_t * const opts, const struct fw_chain * const fwc, const unsigned int proto, - const char * const ip, const unsigned int port, - const unsigned int exp_ts) + const char * const srcip, const char * const dstip, + const unsigned int port, const unsigned int exp_ts) { int rule_exists=0, rule_num=0, rtmp=0; char cmd_buf[CMD_BUFSIZE] = {0}; char target_search[CMD_BUFSIZE] = {0}; char proto_search[CMD_BUFSIZE] = {0}; - char ip_search[CMD_BUFSIZE] = {0}; + char srcip_search[CMD_BUFSIZE] = {0}; + char dstip_search[CMD_BUFSIZE] = {0}; char port_search[CMD_BUFSIZE] = {0}; char exp_ts_search[CMD_BUFSIZE] = {0}; @@ -109,7 +110,11 @@ rule_exists_no_chk_support(const fko_srv_options_t * const opts, snprintf(port_search, CMD_BUFSIZE-1, ":%u ", port); snprintf(target_search, CMD_BUFSIZE-1, " %s ", fwc->target); - snprintf(ip_search, CMD_BUFSIZE-1, " %s ", ip); + snprintf(srcip_search, CMD_BUFSIZE-1, " %s ", srcip); + if (dstip != NULL) + { + snprintf(dstip_search, CMD_BUFSIZE-1, " %s ", dstip); + } snprintf(exp_ts_search, CMD_BUFSIZE-1, "%u ", exp_ts); /* search for each of the substrings, and require the returned @@ -125,25 +130,28 @@ rule_exists_no_chk_support(const fko_srv_options_t * const opts, NO_TIMEOUT, proto_search, &pid_status, opts); if(rtmp == rule_num) rtmp = search_extcmd(cmd_buf, WANT_STDERR, - NO_TIMEOUT, ip_search, &pid_status, opts); + NO_TIMEOUT, srcip_search, &pid_status, opts); if(rtmp == rule_num) - rtmp = search_extcmd(cmd_buf, WANT_STDERR, - NO_TIMEOUT, target_search, &pid_status, opts); + rtmp = (dstip == NULL) ? rtmp : search_extcmd(cmd_buf, WANT_STDERR, + NO_TIMEOUT, dstip_search, &pid_status, opts); if(rtmp == rule_num) rtmp = search_extcmd(cmd_buf, WANT_STDERR, - NO_TIMEOUT, port_search, &pid_status, opts); + NO_TIMEOUT, target_search, &pid_status, opts); if(rtmp == rule_num) - rule_exists = 1; + rtmp = search_extcmd(cmd_buf, WANT_STDERR, + NO_TIMEOUT, port_search, &pid_status, opts); + if(rtmp == rule_num) + rule_exists = 1; } if(rule_exists) log_msg(LOG_DEBUG, "rule_exists_no_chk_support() %s %u -> %s expires: %u rule (already exists", - proto_search, port, ip, exp_ts); + proto_search, port, srcip, exp_ts); else log_msg(LOG_DEBUG, "rule_exists_no_chk_support() %s %u -> %s expires: %u rule does not exist", - proto_search, port, ip, exp_ts); + proto_search, port, srcip, exp_ts); return(rule_exists); } @@ -185,15 +193,16 @@ rule_exists_chk_support(const fko_srv_options_t * const opts, static int rule_exists(const fko_srv_options_t * const opts, const struct fw_chain * const fwc, const char * const rule, - const unsigned int proto, const char * const ip, - const unsigned int port, const unsigned int exp_ts) + const unsigned int proto, const char * const srcip, + const char * const dstip, const unsigned int port, + const unsigned int exp_ts) { int rule_exists = 0; if(have_ipt_chk_support == 1) rule_exists = rule_exists_chk_support(opts, fwc->to_chain, rule); else - rule_exists = rule_exists_no_chk_support(opts, fwc, proto, ip, port, exp_ts); + rule_exists = rule_exists_no_chk_support(opts, fwc, proto, srcip, (opts->fw_config->use_destination ? dstip : NULL), port, exp_ts); if(rule_exists == 1) log_msg(LOG_DEBUG, "rule_exists() Rule : '%s' in %s already exists", @@ -859,6 +868,11 @@ fw_config_init(fko_srv_options_t * const opts) } } } + + if(strncasecmp(opts->config[CONF_ENABLE_DESTINATION_RULE], "Y", 1)==0) + { + fwc.use_destination = 1; + } /* Let us find it via our opts struct as well. */ @@ -1044,13 +1058,14 @@ process_spa_request(const fko_srv_options_t * const opts, in_chain->table, ple->proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : IPT_ANY_IP), ple->port, exp_ts, in_chain->target ); if(rule_exists(opts, in_chain, rule_buf, - ple->proto, spadat->use_src_ip, ple->port, exp_ts) == 0) + ple->proto, spadat->use_src_ip, spadat->pkt_destination_ip, ple->port, exp_ts) == 0) { if(create_rule(opts, in_chain->to_chain, rule_buf)) { @@ -1080,13 +1095,14 @@ process_spa_request(const fko_srv_options_t * const opts, out_chain->table, ple->proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : IPT_ANY_IP), ple->port, exp_ts, out_chain->target ); if(rule_exists(opts, out_chain, rule_buf, - ple->proto, spadat->use_src_ip, ple->port, exp_ts) == 0) + ple->proto, spadat->use_src_ip, spadat->pkt_destination_ip, ple->port, exp_ts) == 0) { if(create_rule(opts, out_chain->to_chain, rule_buf)) { @@ -1153,6 +1169,7 @@ process_spa_request(const fko_srv_options_t * const opts, in_chain->table, fst_proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : IPT_ANY_IP), nat_port, exp_ts, in_chain->target @@ -1168,7 +1185,7 @@ process_spa_request(const fko_srv_options_t * const opts, add_jump_rule(opts, IPT_INPUT_ACCESS); if(rule_exists(opts, in_chain, rule_buf, - fst_proto, spadat->use_src_ip, nat_port, exp_ts) == 0) + fst_proto, spadat->use_src_ip, spadat->pkt_destination_ip, nat_port, exp_ts) == 0) { if(create_rule(opts, in_chain->to_chain, rule_buf)) { @@ -1211,7 +1228,7 @@ process_spa_request(const fko_srv_options_t * const opts, ); if(rule_exists(opts, fwd_chain, rule_buf, fst_proto, - spadat->use_src_ip, nat_port, exp_ts) == 0) + spadat->use_src_ip, nat_ip, nat_port, exp_ts) == 0) { if(create_rule(opts, fwd_chain->to_chain, rule_buf)) { @@ -1247,6 +1264,7 @@ process_spa_request(const fko_srv_options_t * const opts, dnat_chain->table, fst_proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : IPT_ANY_IP), fst_port, exp_ts, dnat_chain->target, @@ -1255,7 +1273,7 @@ process_spa_request(const fko_srv_options_t * const opts, ); if(rule_exists(opts, dnat_chain, rule_buf, fst_proto, - spadat->use_src_ip, fst_port, exp_ts) == 0) + spadat->use_src_ip, spadat->pkt_destination_ip, fst_port, exp_ts) == 0) { if(create_rule(opts, dnat_chain->to_chain, rule_buf)) { @@ -1335,7 +1353,7 @@ process_spa_request(const fko_srv_options_t * const opts, ); if(rule_exists(opts, snat_chain, rule_buf, fst_proto, - spadat->use_src_ip, nat_port, exp_ts) == 0) + spadat->use_src_ip, NULL, nat_port, exp_ts) == 0) { if(create_rule(opts, snat_chain->to_chain, rule_buf)) { diff --git a/server/fw_util_iptables.h b/server/fw_util_iptables.h index 626e0ec5..27794efc 100644 --- a/server/fw_util_iptables.h +++ b/server/fw_util_iptables.h @@ -42,10 +42,10 @@ /* iptables command args */ #define IPT_CHK_RULE_ARGS "-C %s %s" /* the other macros add SH_REDIR if necessary */ -#define IPT_RULE_ARGS "-t %s -p %i -s %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR -#define IPT_OUT_RULE_ARGS "-t %s -p %i -d %s --sport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR +#define IPT_RULE_ARGS "-t %s -p %i -s %s -d %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR +#define IPT_OUT_RULE_ARGS "-t %s -p %i -d %s -s %s --sport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR #define IPT_FWD_RULE_ARGS "-t %s -p %i -s %s -d %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s" SH_REDIR -#define IPT_DNAT_RULE_ARGS "-t %s -p %i -s %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s --to-destination %s:%i" SH_REDIR +#define IPT_DNAT_RULE_ARGS "-t %s -p %i -s %s -d %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s --to-destination %s:%i" SH_REDIR #define IPT_SNAT_RULE_ARGS "-t %s -p %i -d %s --dport %i -m comment --comment " EXPIRE_COMMENT_PREFIX "%u -j %s %s" SH_REDIR #define IPT_TMP_COMMENT_ARGS "-t %s -I %s %i -s 127.0.0.2 -m comment --comment " TMP_COMMENT " -j %s" SH_REDIR #define IPT_TMP_CHK_RULE_ARGS "-t %s -I %s %i -s 127.0.0.2 -p udp -j %s" SH_REDIR @@ -60,6 +60,7 @@ #define IPT_DEL_JUMP_RULE_ARGS "-t %s -D %s -j %s" SH_REDIR /* let iptables work out the rule number */ #define IPT_LIST_RULES_ARGS "-t %s -L %s --line-numbers -n" SH_REDIR #define IPT_LIST_ALL_RULES_ARGS "-t %s -v -n -L --line-numbers" SH_REDIR +#define IPT_ANY_IP "0.0.0.0/0" int validate_ipt_chain_conf(const char * const chain_str); diff --git a/server/fw_util_pf.c b/server/fw_util_pf.c index 9d76033c..f41bf7a7 100644 --- a/server/fw_util_pf.c +++ b/server/fw_util_pf.c @@ -151,6 +151,11 @@ fw_config_init(fko_srv_options_t * const opts) /* Set the PF anchor name */ strlcpy(fwc.anchor, opts->config[CONF_PF_ANCHOR_NAME], sizeof(fwc.anchor)); + + if(strncasecmp(opts->config[CONF_ENABLE_DESTINATION_RULE], "Y", 1)==0) + { + fwc.use_destination = 1; + } /* Let us find it via our opts struct as well. */ @@ -243,6 +248,7 @@ process_spa_request(const fko_srv_options_t * const opts, snprintf(new_rule, MAX_PF_NEW_RULE_LEN-1, PF_ADD_RULE_ARGS "\n", ple->proto, spadat->use_src_ip, + (fwc.use_destination ? spadat->pkt_destination_ip : PF_ANY_IP), ple->port, exp_ts ); diff --git a/server/fw_util_pf.h b/server/fw_util_pf.h index e1c6f093..9d4901eb 100644 --- a/server/fw_util_pf.h +++ b/server/fw_util_pf.h @@ -32,7 +32,7 @@ #define FW_UTIL_PF_H #define MAX_PF_ANCHOR_SEARCH_LEN (MAX_PF_ANCHOR_LEN+11) /* room for 'anchor "' string */ -#define MAX_PF_NEW_RULE_LEN 120 +#define MAX_PF_NEW_RULE_LEN 140 #if HAVE_EXECVPE #define SH_REDIR "" /* the shell is not used when execvpe() is available */ @@ -42,7 +42,7 @@ /* pf command args */ -#define PF_ADD_RULE_ARGS "pass in quick proto %u from %s to any port %u keep state label " EXPIRE_COMMENT_PREFIX "%u" +#define PF_ADD_RULE_ARGS "pass in quick proto %u from %s to %s port %u keep state label " EXPIRE_COMMENT_PREFIX "%u" #define PF_WRITE_ANCHOR_RULES_ARGS "-a %s -f -" #if HAVE_EXECVPE #define PF_LIST_ANCHOR_RULES_ARGS "-a %s -s rules" @@ -51,6 +51,7 @@ #endif #define PF_ANCHOR_CHECK_ARGS "-s Anchor" SH_REDIR /* to check for fwknop anchor */ #define PF_DEL_ALL_ANCHOR_RULES "-a %s -F all" SH_REDIR +#define PF_ANY_IP "any" #endif /* FW_UTIL_PF_H */ diff --git a/server/fwknopd.8.in b/server/fwknopd.8.in index 7663ecbe..6b167aa7 100644 --- a/server/fwknopd.8.in +++ b/server/fwknopd.8.in @@ -526,6 +526,13 @@ Specify the directory where writes run time state files\&. The default is \fI@localstatedir@/run\fR\&. .RE +.PP +\fBENABLE_DESTINATION_RULE\fR \fI\fR +.RS 4 +Controls whether +\fBfwknopd\fR +will set the destination field on the firewall rule to the destination address specified on the incoming SPA packet\&. This is useful for interfaces with multiple IP addresses hosting separate services\&. If \(lqENABLE_IPT_OUTPUT\(rq is set to \(lqY\(rq, the source field of the firewall rule is set\&. FORWARD and SNAT rules are not affected however, DNAT rules will also have their destination field set\&. The default is \(lqN\(rq, which sets the destination field to 0\&.0\&.0\&.0/0 (any)\&. +.RE .SS "ACCESS\&.CONF VARIABLES" .sp This section describes the access control directives in the \fI@sysconfdir@/fwknop/access\&.conf\fR file\&. Theses directives define encryption keys and level of access that is granted to \fBfwknop\fR clients that have generated the appropriate encrypted message\&. @@ -539,6 +546,11 @@ This defines the source address from which the SPA packet will be accepted\&. Th definition must start with the \(lqSOURCE\(rq keyword\&. Networks should be specified in CIDR notation (e\&.g\&. \(lq192\&.168\&.10\&.0/24\(rq), and individual IP addresses can be specified as well\&. Also, multiple IP\(cqs and/or networks can be defined as a comma separated list (e\&.g\&. \(lq192\&.168\&.10\&.0/24,10\&.1\&.1\&.123\(rq) .RE .PP +\fBDESTINATION\fR \fI\fR +.RS 4 +This defines the destination address to which the SPA packet will be accepted\&. The string \(lqANY\(rq is also accepted if a valid SPA packet should be honored to any destination IP\&. Networks should be specified in CIDR notation (e\&.g\&. \(lq192\&.168\&.10\&.0/24\(rq), and individual IP addresses can be specified as well\&. Also, multiple IP\(cqs and/or networks can be defined as a comma separated list (e\&.g\&. \(lq192\&.168\&.10\&.0/24,10\&.1\&.1\&.123\(rq) +.RE +.PP \fBOPEN_PORTS\fR \fI,\&...,\fR .RS 4 Define a set of ports and protocols (tcp or udp) that will be opened if a valid knock sequence is seen\&. If this entry is not set, diff --git a/server/fwknopd.conf b/server/fwknopd.conf index 8faeebd6..29ec9e2d 100644 --- a/server/fwknopd.conf +++ b/server/fwknopd.conf @@ -152,6 +152,16 @@ # # ENABLE_PCAP_ANY_DIRECTION N; +# Controls whether fwknopd will set the destination field on the firewall +# rule to the destination address specified on the incoming SPA packet. +# This is useful for interfaces with multiple IP addresses hosting separate +# services. If ENABLE_IPT_OUTPUT is set to "Y", the source field of +# the firewall rule is set. FORWARD and SNAT rules are not affected however, +# DNAT rules will also have their destination field set. The default is +# "N", which sets the destination field to 0.0.0.0/0 (any). +# +# ENABLE_DESTINATION_RULE Y; + ############################################################################## # NOTE: The following EXTERNAL_CMD functionality is not yet implemented. # This is a possible future feature of fwknopd. diff --git a/server/fwknopd_common.h b/server/fwknopd_common.h index f06b6e15..350aeb3d 100644 --- a/server/fwknopd_common.h +++ b/server/fwknopd_common.h @@ -110,6 +110,7 @@ #define DEF_UDPSERV_SELECT_TIMEOUT "500000" /* half a second (in microseconds) */ #define DEF_SYSLOG_IDENTITY MY_NAME #define DEF_SYSLOG_FACILITY "LOG_DAEMON" +#define DEF_ENABLE_DESTINATION_RULE "N" #define DEF_FW_ACCESS_TIMEOUT 30 @@ -247,6 +248,7 @@ enum { //CONF_EXTERNAL_CMD_ALARM, //CONF_ENABLE_EXT_CMD_PREFIX, //CONF_EXT_CMD_PREFIX, + CONF_ENABLE_DESTINATION_RULE, #if FIREWALL_FIREWALLD CONF_ENABLE_FIREWD_FORWARDING, CONF_ENABLE_FIREWD_LOCAL_NAT, @@ -345,6 +347,8 @@ typedef struct acc_stanza { char *source; acc_int_list_t *source_list; + char *destination; + acc_int_list_t *destination_list; char *open_ports; acc_port_list_t *oport_list; char *restrict_ports; @@ -446,6 +450,9 @@ typedef struct acc_stanza /* Flag for firewalld SNAT vs. MASQUERADE usage */ unsigned char use_masquerade; + /* Flag for setting destination field in rule + */ + unsigned char use_destination; }; #elif FIREWALL_IPTABLES @@ -493,6 +500,9 @@ typedef struct acc_stanza /* Flag for iptables SNAT vs. MASQUERADE usage */ unsigned char use_masquerade; + /* Flag for setting destination field in rule + */ + unsigned char use_destination; }; #elif FIREWALL_IPFW @@ -509,6 +519,7 @@ typedef struct acc_stanza time_t next_expire; time_t last_purge; char fw_command[MAX_PATH_LEN]; + unsigned char use_destination; }; #elif FIREWALL_PF @@ -520,6 +531,7 @@ typedef struct acc_stanza time_t next_expire; char anchor[MAX_PF_ANCHOR_LEN]; char fw_command[MAX_PATH_LEN]; + unsigned char use_destination; }; #elif FIREWALL_IPF @@ -552,6 +564,7 @@ typedef struct spa_data char *spa_message; char spa_message_src_ip[MAX_IPV4_STR_LEN]; char pkt_source_ip[MAX_IPV4_STR_LEN]; + char pkt_destination_ip[MAX_IPV4_STR_LEN]; char spa_message_remain[1024]; /* --DSS FIXME: arbitrary bounds */ char *nat_access; char *server_auth; diff --git a/server/incoming_spa.c b/server/incoming_spa.c index 544f102e..a49291c0 100644 --- a/server/incoming_spa.c +++ b/server/incoming_spa.c @@ -320,6 +320,9 @@ incoming_spa(fko_srv_options_t *opts) inet_ntop(AF_INET, &(spa_pkt->packet_src_ip), spadat.pkt_source_ip, sizeof(spadat.pkt_source_ip)); + + inet_ntop(AF_INET, &(spa_pkt->packet_dst_ip), + spadat.pkt_destination_ip, sizeof(spadat.pkt_destination_ip)); /* At this point, we want to validate and (if needed) preprocess the * SPA data and/or to be reasonably sure we have a SPA packet (i.e @@ -405,9 +408,10 @@ incoming_spa(fko_srv_options_t *opts) ctx = NULL; } - /* Check for a match for the SPA source IP and the access stanza + /* Check for a match for the SPA source and destination IP and the access stanza */ - if(! compare_addr_list(acc->source_list, ntohl(spa_pkt->packet_src_ip))) + if(! compare_addr_list(acc->source_list, ntohl(spa_pkt->packet_src_ip)) || + (acc->destination_list != NULL && ! compare_addr_list(acc->destination_list, ntohl(spa_pkt->packet_dst_ip)))) { acc = acc->next; continue; diff --git a/test/conf/destination_rule_fwknopd.conf b/test/conf/destination_rule_fwknopd.conf new file mode 100644 index 00000000..670a3ca5 --- /dev/null +++ b/test/conf/destination_rule_fwknopd.conf @@ -0,0 +1 @@ +ENABLE_DESTINATION_RULE Y; diff --git a/test/tests/basic_operations.pl b/test/tests/basic_operations.pl index 91f3ecb1..93eb3351 100644 --- a/test/tests/basic_operations.pl +++ b/test/tests/basic_operations.pl @@ -3335,6 +3335,22 @@ 'positive_output_matches' => [qr/No\skeys\sfound/], }, + { + 'category' => 'basic operations', + 'subcategory' => 'server', + 'detail' => 'access DESTINATION key', + 'function' => \&server_conf_files, + 'fwknopd_cmdline' => $server_rewrite_conf_files, + 'exec_err' => $YES, + 'server_access_file' => [ + 'DESTINATION 1.1.1.1', + ], + 'server_conf_file' => [ + '### comment line' + ], + 'positive_output_matches' => [qr/No\skeys\sfound/], + }, + { 'category' => 'basic operations', 'subcategory' => 'server',