From e0114e60c26727268b2b16b3098e8fb117d4a449 Mon Sep 17 00:00:00 2001 From: Michael Rash Date: Wed, 4 Dec 2013 21:52:07 -0500 Subject: [PATCH] [server] Added FORCE_SNAT to access.conf stanzas. Added FORCE_SNAT to the access.conf file so that per-access stanza SNAT criteria can be specified for SPA access. --- ChangeLog | 2 ++ doc/fwknopd.man.asciidoc | 10 +++++++ server/access.c | 57 ++++++++++++++++++++++++++++++++++++- server/fw_util_iptables.c | 46 ++++++++++++++---------------- server/fwknopd_common.h | 9 ++++++ test/test-fwknop.pl | 3 ++ test/tests/rijndael_hmac.pl | 47 +++++++++++++++++++++++++++--- 7 files changed, 144 insertions(+), 30 deletions(-) diff --git a/ChangeLog b/ChangeLog index 34f97642..39154239 100644 --- a/ChangeLog +++ b/ChangeLog @@ -29,6 +29,8 @@ fwknop-2.5.2 (//2013): - [server] Bug fix for SPA NAT modes on iptables firewalls to ensure that custom fwknop chains are re-created if they get deleted out from under the running fwknopd instance. + - [server] Added FORCE_SNAT to the access.conf file so that per-access + stanza SNAT criteria can be specified for SPA access. - [test suite] added --gdb-test to allow a previously executed fwknop or fwknopd command to be sent through gdb with the same command line args as the test suite used. This is for convenience to rapidly allow diff --git a/doc/fwknopd.man.asciidoc b/doc/fwknopd.man.asciidoc index c39bf50c..d1d9da1f 100644 --- a/doc/fwknopd.man.asciidoc +++ b/doc/fwknopd.man.asciidoc @@ -443,6 +443,16 @@ directive starts a new stanza. for each stanza in the access.conf file. This way, multiple external users can each directly access only one internal system per SPA key. +*FORCE_SNAT* '':: + For any valid SPA packet, add an SNAT rule in addition to any DNAT rule + created with a corresponding (required) FORCE_NAT variable. This is + analogous to SNAT_TRANSLATE_IP from the '@sysconfdir@/fwknop/fwknopd.conf'' file + except that it is per access stanza and overrides any value set with + SNAT_TRANSLATE_IP. This is useful for situations where an incoming NAT'd + connection may be otherwise unanswerable due to routing constraints (i.e. + the system receiving the SPA authenticated connection has a default route + to a different device than the SPA system itself). + *GPG_HOME_DIR* '':: Define the path to the GnuPG directory to be used by the *fwknopd* server. If this keyword is not specified within '@sysconfdir@/fwknop/access.conf' then diff --git a/server/access.c b/server/access.c index 203d2d89..88699f72 100644 --- a/server/access.c +++ b/server/access.c @@ -165,7 +165,6 @@ add_acc_force_nat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *v if (sscanf(val, "%15s %5u", ip_str, &curr_acc->force_nat_port) != 2) { - log_msg(LOG_ERR, "[*] Fatal: invalid FORCE_NAT arg '%s', need ", val @@ -192,6 +191,32 @@ add_acc_force_nat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *v return; } + +static void +add_acc_force_snat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *val) +{ + char ip_str[MAX_IPV4_STR_LEN] = {0}; + + if (sscanf(val, "%15s", ip_str) != 1) + { + log_msg(LOG_ERR, + "[*] Fatal: invalid FORCE_SNAT arg '%s', need ", val); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + + if(! is_valid_ipv4_addr(ip_str)) + { + log_msg(LOG_ERR, + "[*] Fatal: invalid FORCE_NAT IP '%s'", ip_str); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + + curr_acc->force_snat = 1; + add_acc_string(&(curr_acc->force_snat_ip), ip_str); + + return; +} + #endif /* Take an IP or Subnet/Mask and convert it to mask for later @@ -680,6 +705,9 @@ free_acc_stanza_data(acc_stanza_t *acc) if(acc->force_nat_ip != NULL) free(acc->force_nat_ip); + if(acc->force_nat_ip != NULL) + free(acc->force_snat_ip); + if(acc->key != NULL) { zero_buf_wrapper(acc->key, acc->key_len); @@ -955,6 +983,15 @@ acc_data_is_valid(acc_stanza_t * const acc) } } + if(acc->force_snat == 1 && acc->force_nat == 0) + { + log_msg(LOG_ERR, + "[*] FORCE_SNAT implies FORCE_NAT must also be used for access stanza source: '%s'", + acc->source + ); + return(0); + } + if(acc->require_source_address == 0) { log_msg(LOG_INFO, @@ -1338,6 +1375,24 @@ parse_access_file(fko_srv_options_t *opts) "[*] FORCE_NAT not supported."); fclose(file_ptr); clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); +#endif + } + else if(CONF_VAR_IS(var, "FORCE_SNAT")) + { +#if FIREWALL_IPTABLES + if(strncasecmp(opts->config[CONF_ENABLE_IPT_FORWARDING], "Y", 1) !=0 ) + { + log_msg(LOG_ERR, + "[*] FORCE_SNAT_NAT requires ENABLE_IPT_FORWARDING to be enabled in fwknopd.conf"); + fclose(file_ptr); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + add_acc_force_snat(opts, curr_acc, val); +#else + log_msg(LOG_ERR, + "[*] FORCE_SNAT not supported."); + fclose(file_ptr); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); #endif } else diff --git a/server/fw_util_iptables.c b/server/fw_util_iptables.c index 8a103041..5460430a 100644 --- a/server/fw_util_iptables.c +++ b/server/fw_util_iptables.c @@ -961,7 +961,7 @@ process_spa_request(const fko_srv_options_t * const opts, struct fw_chain * const dnat_chain = &(opts->fw_config->chain[IPT_DNAT_ACCESS]); struct fw_chain *snat_chain; /* We assign this later (if we need to). */ - int res = 0, is_err; + int res = 0, is_err, snat_chain_num = 0; time_t now; unsigned int exp_ts; @@ -1260,29 +1260,27 @@ process_spa_request(const fko_srv_options_t * const opts, /* If SNAT (or MASQUERADE) is wanted, then we add those rules here as well. */ - if(strncasecmp(opts->config[CONF_ENABLE_IPT_SNAT], "Y", 1) == 0) + if(acc->force_snat || strncasecmp(opts->config[CONF_ENABLE_IPT_SNAT], "Y", 1) == 0) { - /* Setup some parameter depending on whether we are using SNAT - * or MASQUERADE. + /* Add SNAT or MASQUERADE rules. */ - if((opts->config[CONF_SNAT_TRANSLATE_IP] != NULL) - && strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "__CHANGEME__", 10)!=0) + if(acc->force_snat && is_valid_ipv4_addr(acc->force_snat_ip)) + { + /* Using static SNAT */ + snat_chain = &(opts->fw_config->chain[IPT_SNAT_ACCESS]); + snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, + "--to-source %s:%i", acc->force_snat_ip, fst_port); + snat_chain_num = IPT_SNAT_ACCESS; + } + else if((opts->config[CONF_SNAT_TRANSLATE_IP] != NULL) + && is_valid_ipv4_addr(opts->config[CONF_SNAT_TRANSLATE_IP])) { /* Using static SNAT */ snat_chain = &(opts->fw_config->chain[IPT_SNAT_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-source %s:%i", opts->config[CONF_SNAT_TRANSLATE_IP], fst_port); - - /* Check to make sure that the jump rules exist for each - * required chain - */ - if(chain_exists(opts, IPT_SNAT_ACCESS) == 0) - create_chain(opts, IPT_SNAT_ACCESS); - - if(jump_rule_exists(opts, IPT_SNAT_ACCESS) == 0) - add_jump_rule(opts, IPT_SNAT_ACCESS); - + snat_chain_num = IPT_SNAT_ACCESS; } else { @@ -1290,17 +1288,15 @@ process_spa_request(const fko_srv_options_t * const opts, snat_chain = &(opts->fw_config->chain[IPT_MASQUERADE_ACCESS]); snprintf(snat_target, SNAT_TARGET_BUFSIZE-1, "--to-ports %i", fst_port); - - /* Check to make sure that the jump rules exist for each - * required chain - */ - if(chain_exists(opts, IPT_MASQUERADE_ACCESS) == 0) - create_chain(opts, IPT_MASQUERADE_ACCESS); - - if(jump_rule_exists(opts, IPT_MASQUERADE_ACCESS) == 0) - add_jump_rule(opts, IPT_MASQUERADE_ACCESS); + snat_chain_num = IPT_MASQUERADE_ACCESS; } + if(chain_exists(opts, snat_chain_num) == 0) + create_chain(opts, snat_chain_num); + + if(jump_rule_exists(opts, snat_chain_num) == 0) + add_jump_rule(opts, snat_chain_num); + memset(rule_buf, 0, CMD_BUFSIZE); snprintf(rule_buf, CMD_BUFSIZE-1, IPT_SNAT_RULE_ARGS, diff --git a/server/fwknopd_common.h b/server/fwknopd_common.h index 9c2a7d60..b06c8490 100644 --- a/server/fwknopd_common.h +++ b/server/fwknopd_common.h @@ -321,10 +321,19 @@ typedef struct acc_stanza time_t access_expire_time; int expired; int encryption_mode; + + /* DNAT parameters + */ unsigned char force_nat; char *force_nat_ip; char *force_nat_proto; unsigned int force_nat_port; + + /* SNAT parameters + */ + unsigned char force_snat; + char *force_snat_ip; + struct acc_stanza *next; } acc_stanza_t; diff --git a/test/test-fwknop.pl b/test/test-fwknop.pl index 42aef8fa..0a6905c6 100755 --- a/test/test-fwknop.pl +++ b/test/test-fwknop.pl @@ -79,6 +79,7 @@ our %cf = ( 'invalid_ipt_input_chain6' => "$conf_dir/invalid_ipt_input_chain_6_fwknopd.conf", 'force_nat_access' => "$conf_dir/force_nat_access.conf", 'hmac_force_nat_access' => "$conf_dir/hmac_force_nat_access.conf", + 'hmac_force_snat_access' => "$conf_dir/hmac_force_snat_access.conf", 'cmd_access' => "$conf_dir/cmd_access.conf", 'local_nat' => "$conf_dir/local_nat_fwknopd.conf", 'no_flush_init' => "$conf_dir/no_flush_init_fwknopd.conf", @@ -186,6 +187,8 @@ our $fake_ip = '127.0.0.2'; our $spoof_ip = '1.2.3.4'; our $internal_nat_host = '192.168.1.2'; our $force_nat_host = '192.168.1.123'; +our $force_nat_host2 = '123.4.4.4'; +our $force_snat_host = '33.3.3.3'; our $default_spa_port = 62201; our $non_std_spa_port = 12345; diff --git a/test/tests/rijndael_hmac.pl b/test/tests/rijndael_hmac.pl index 64eeffa2..3d65639e 100644 --- a/test/tests/rijndael_hmac.pl +++ b/test/tests/rijndael_hmac.pl @@ -846,13 +846,52 @@ $cf{'rc_hmac_b64_key'}, 'fwknopd_cmdline' => "$fwknopdCmd -c $cf{'nat'} -a $cf{'hmac_force_nat_access'} " . "-d $default_digest_file -p $default_pid_file $intf_str", - 'server_positive_output_matches' => [qr/\sto\:$force_nat_host\:22/i], - 'server_negative_output_matches' => [qr/\sto\:$internal_nat_host\:22/i], + 'server_positive_output_matches' => [qr/\*\/\sto\:$force_nat_host\:22/i], + 'server_negative_output_matches' => [qr/\*\/\sto\:$internal_nat_host\:22/i], 'fw_rule_created' => $NEW_RULE_REQUIRED, 'fw_rule_removed' => $NEW_RULE_REMOVED, 'server_conf' => $cf{'nat'}, 'key_file' => $cf{'rc_hmac_b64_key'}, }, + { + 'category' => 'Rijndael+HMAC', + 'subcategory' => 'client+server', + 'detail' => "force SNAT $force_snat_host (tcp/22)", + 'function' => \&spa_cycle, + 'cmdline' => $default_client_args, + 'cmdline' => "$default_client_args_no_get_key --rc-file " . + $cf{'rc_hmac_b64_key'}, + 'fwknopd_cmdline' => "$fwknopdCmd -c $cf{'snat'} -a $cf{'hmac_force_snat_access'} " . + "-d $default_digest_file -p $default_pid_file $intf_str", + 'server_positive_output_matches' => [qr/DNAT\s.*\*\/\sto\:$force_nat_host2\:22/i, + qr/SNAT\s.*\*\/\sto\:$force_snat_host\:22/], + 'server_negative_output_matches' => [qr/\*\/\sto\:$internal_nat_host\:22/i, + qr/\*\/\sto\:$force_nat_host\:22/i], + 'fw_rule_created' => $NEW_RULE_REQUIRED, + 'fw_rule_removed' => $NEW_RULE_REMOVED, + 'server_conf' => $cf{'snat'}, + 'key_file' => $cf{'rc_hmac_b64_key'}, + }, + { + 'category' => 'Rijndael+HMAC', + 'subcategory' => 'client+server', + 'detail' => "force SNAT $force_snat_host (ipt flush)", + 'function' => \&spa_cycle, + 'cmdline' => $default_client_args, + 'cmdline' => "$default_client_args_no_get_key --rc-file " . + $cf{'rc_hmac_b64_key'}, + 'fwknopd_cmdline' => "$fwknopdCmd -c $cf{'snat'} -a $cf{'hmac_force_snat_access'} " . + "-d $default_digest_file -p $default_pid_file $intf_str", + 'server_positive_output_matches' => [qr/DNAT\s.*\*\/\sto\:$force_nat_host2\:22/i, + qr/SNAT\s.*\*\/\sto\:$force_snat_host\:22/], + 'server_negative_output_matches' => [qr/\*\/\sto\:$internal_nat_host\:22/i, + qr/\*\/\sto\:$force_nat_host\:22/i], + 'fw_rule_created' => $NEW_RULE_REQUIRED, + 'fw_rule_removed' => $NEW_RULE_REMOVED, + 'server_conf' => $cf{'snat'}, + 'key_file' => $cf{'rc_hmac_b64_key'}, + 'iptables_rm_chains_after_server_start' => $YES, + }, { 'category' => 'Rijndael+HMAC', 'subcategory' => 'client+server', @@ -863,8 +902,8 @@ $cf{'rc_hmac_b64_key'}, 'fwknopd_cmdline' => "$fwknopdCmd -c $cf{'nat'} -a $cf{'hmac_force_nat_access'} " . "-d $default_digest_file -p $default_pid_file $intf_str", - 'server_positive_output_matches' => [qr/\sto\:$force_nat_host\:22/i], - 'server_negative_output_matches' => [qr/\sto\:$internal_nat_host\:22/i], + 'server_positive_output_matches' => [qr/\*\/\sto\:$force_nat_host\:22/i], + 'server_negative_output_matches' => [qr/\*\/\sto\:$internal_nat_host\:22/i], 'fw_rule_created' => $NEW_RULE_REQUIRED, 'fw_rule_removed' => $NEW_RULE_REMOVED, 'server_conf' => $cf{'nat'},